<!DOCTYPE html>



  


<html class="theme-next gemini use-motion" lang="zh-Hans">
<head>
  <meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
<meta name="theme-color" content="#222">






  
  
  <link rel="stylesheet" media="all" href="/lib/Han/dist/han.min.css?v=3.3">




<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />
















  
  
  <link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css" />







<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css" />

<link href="/css/main.css?v=5.1.4" rel="stylesheet" type="text/css" />


  <link rel="apple-touch-icon" sizes="180x180" href="/images/favicon.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon.png?v=5.1.4">


  <link rel="mask-icon" href="/images/favicon.png?v=5.1.4" color="#222">





  <meta name="keywords" content="C++," />





  <link rel="alternate" href="/atom.xml" title="平步青云win" type="application/atom+xml" />






<meta name="description" content="面向对象编程的主要目的之一是提供可重用的代码。C++类提供了更多层次的重用性。目前，很多厂商提供了类库，类库由类声明和类实现构成。因为类组合了数组表示和类方法，因此提供了比函数库更加完整的程序包。类库是以源代码的方式提供，这意味着可以对其进行修改，以满足需要。然而，C++提供了比修改代码更好的方式来扩展和修改类。这种方法叫作类继承。它能够从已有的类派生出新的类，而派生类继承了原有类(称为基类)的特">
<meta name="keywords" content="C++">
<meta property="og:type" content="article">
<meta property="og:title" content="C++ Primer Plus--类继承（十三）">
<meta property="og:url" content="https://zxpgo.github.io/2019/02/18/C++ Primer Plus--类继承（十三）/index.html">
<meta property="og:site_name" content="平步青云win">
<meta property="og:description" content="面向对象编程的主要目的之一是提供可重用的代码。C++类提供了更多层次的重用性。目前，很多厂商提供了类库，类库由类声明和类实现构成。因为类组合了数组表示和类方法，因此提供了比函数库更加完整的程序包。类库是以源代码的方式提供，这意味着可以对其进行修改，以满足需要。然而，C++提供了比修改代码更好的方式来扩展和修改类。这种方法叫作类继承。它能够从已有的类派生出新的类，而派生类继承了原有类(称为基类)的特">
<meta property="og:locale" content="zh-Hans">
<meta property="og:image" content="https://i.imgur.com/aXeud2q.png">
<meta property="og:image" content="https://i.imgur.com/9rsExmz.png">
<meta property="og:image" content="https://i.imgur.com/EAn3eli.png">
<meta property="og:image" content="https://i.imgur.com/1W3t0Wd.png">
<meta property="og:image" content="https://i.imgur.com/hEf8Ccr.png">
<meta property="og:updated_time" content="2019-02-23T14:35:55.143Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="C++ Primer Plus--类继承（十三）">
<meta name="twitter:description" content="面向对象编程的主要目的之一是提供可重用的代码。C++类提供了更多层次的重用性。目前，很多厂商提供了类库，类库由类声明和类实现构成。因为类组合了数组表示和类方法，因此提供了比函数库更加完整的程序包。类库是以源代码的方式提供，这意味着可以对其进行修改，以满足需要。然而，C++提供了比修改代码更好的方式来扩展和修改类。这种方法叫作类继承。它能够从已有的类派生出新的类，而派生类继承了原有类(称为基类)的特">
<meta name="twitter:image" content="https://i.imgur.com/aXeud2q.png">



<script type="text/javascript" id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '/',
    scheme: 'Gemini',
    version: '5.1.4',
    sidebar: {"position":"right","display":"post","offset":10,"b2t":false,"scrollpercent":false,"onmobile":false},
    fancybox: true,
    tabs: true,
    motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
    duoshuo: {
      userId: '0',
      author: '博主'
    },
    algolia: {
      applicationID: 'PAO8LM7QB1',
      apiKey: '',
      indexName: 'Blog',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    }
  };
</script>



  <link rel="canonical" href="https://zxpgo.github.io/2019/02/18/C++ Primer Plus--类继承（十三）/"/>





  <title>C++ Primer Plus--类继承（十三） | 平步青云win</title>
  





  <script type="text/javascript">
    var _hmt = _hmt || [];
    (function() {
      var hm = document.createElement("script");
      hm.src = "https://hm.baidu.com/hm.js?7a4517a3ce6d7c50203655d056f01ac3";
      var s = document.getElementsByTagName("script")[0];
      s.parentNode.insertBefore(hm, s);
    })();
  </script>




</head>

<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">

  
  
    
  

  <div class="container sidebar-position-right page-post-detail">
    <div class="headband"></div>

    <header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-wrapper">
  <div class="site-meta ">
    

    <div class="custom-logo-site-title">
      <a href="/"  class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">平步青云win</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
      
        <h1 class="site-subtitle" itemprop="description"></h1>
      
  </div>

  <div class="site-nav-toggle">
    <button>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>

<nav class="site-nav">
  

  
    <ul id="menu" class="menu">
      
        
        <li class="menu-item menu-item-home">
          <a href="/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-home"></i> <br />
            
            首页
          </a>
        </li>
      
        
        <li class="menu-item menu-item-about">
          <a href="/about/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-user"></i> <br />
            
            关于
          </a>
        </li>
      
        
        <li class="menu-item menu-item-tags">
          <a href="/tags/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-tags"></i> <br />
            
            标签
          </a>
        </li>
      
        
        <li class="menu-item menu-item-categories">
          <a href="/categories/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-th"></i> <br />
            
            分类
          </a>
        </li>
      
        
        <li class="menu-item menu-item-archives">
          <a href="/archives/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-archive"></i> <br />
            
            归档
          </a>
        </li>
      
        
        <li class="menu-item menu-item-随笔">
          <a href="/sui" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-question-circle"></i> <br />
            
            随笔
          </a>
        </li>
      

      
        <li class="menu-item menu-item-search">
          
            <a href="javascript:;" class="popup-trigger">
			
          
            
              <i class="menu-item-icon fa fa-search fa-fw"></i> <br />
            
            搜索
          </a>
        </li>
      
    </ul>
  

  
    <div class="site-search">
      
  <div class="popup search-popup local-search-popup">
  <div class="local-search-header clearfix">
    <span class="search-icon">
      <i class="fa fa-search"></i>
    </span>
    <span class="popup-btn-close">
      <i class="fa fa-times-circle"></i>
    </span>
    <div class="local-search-input-wrapper">
      <input autocomplete="off"
             placeholder="搜索..." spellcheck="false"
             type="text" id="local-search-input">
    </div>
  </div>
  <div id="local-search-result"></div>
</div>



    </div>
  
</nav>



 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            

  <div id="posts" class="posts-expand">
    

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://zxpgo.github.io/2019/02/18/C++ Primer Plus--类继承（十三）/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="zxp">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="平步青云win">
    </span>

    
      <header class="post-header">

        
        
          <h2 class="post-title" itemprop="name headline">C++ Primer Plus--类继承（十三）</h2>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2019-02-18T19:41:14+08:00">
                2019-02-18
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/C-Primer-Plus学习笔记/" itemprop="url" rel="index">
                    <span itemprop="name">C++ Primer Plus学习笔记</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          
             <span id="/2019/02/18/C++ Primer Plus--类继承（十三）/" class="leancloud_visitors" data-flag-title="C++ Primer Plus--类继承（十三）">
               <span class="post-meta-divider">|</span>
               <span class="post-meta-item-icon">
                 <i class="fa fa-eye"></i>
               </span>
               
                 <span class="post-meta-item-text">阅读次数&#58;</span>
               
                 <span class="leancloud-visitors-count"></span>
             </span>
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body han-init-context" itemprop="articleBody">

      
      

      
        <p>面向对象编程的主要目的之一是提供可重用的代码。C++类提供了更多层次的重用性。目前，很多厂商提供了类库，类库由类声明和类实现构成。因为类组合了数组表示和类方法，因此提供了比函数库更加完整的程序包。类库是以源代码的方式提供，这意味着可以对其进行修改，以满足需要。然而，C++提供了比修改代码更好的方式来扩展和修改类。这种方法叫作<strong>类继承</strong>。它能够从已有的类派生出新的类，而派生类继承了原有类(称为基类)的特征，包括方法。<a id="more"></a></p>
<p>当然，可以通过复制原始代码，并对其进行修改来完成上述工作，但继承机制只需要提供新特性，甚至不需要访问源代码就可以派生出类。因此，如果购买的类库只提供了头文件和编译后代码，仍可以使用库中的类派生出新的类。而且可以在不公开实现的情况下将自己的类分发给其他仍，同时允许他们在类中添加新特性。</p>
<h2 id="13-1-一个简单的基类"><a href="#13-1-一个简单的基类" class="headerlink" title="13.1 一个简单的基类"></a>13.1 一个简单的基类</h2><p>从一个类派生出另一个类时，原始类称为基类，继承类称为派生类。为说明继承，首先需要一个基类。Webtown俱乐部决定跟踪乒乓球会会员。作为俱乐部的首席程序员，需要设计一个简单的TableTennisPlayer类。如程序13.1和13.2所示。</p>
<p>程序13.1 tabtenn0.h</p>
<pre><code>#ifndef TABTENN0_H_
#define TABTENN0_H_
#include &lt;string&gt;
using std::string;

class TableTennisPlayer
{
private:
    string firstname;
    string lastname;
    bool hasTable;
public:
    TableTennisPlayer(const string &amp; fn = &quot;none&quot;,
              const string &amp; ln = &quot;none&quot;,
              bool ht = false);
    void Name() const;
    bool HasTable() const {return hasTable;}
    void ResetTable(bool v) {hasTable = v;}
};

#endif
</code></pre><p>程序13.2 tabtenn0.cpp</p>
<pre><code>#include &quot;tabtenn0.h&quot;
#include &lt;iostream&gt;

TableTennisPlayer::TableTennisPlayer(const string &amp; fn,
    const string &amp; ln, bool ht):firstname(fn), 
        lastname(ln),hasTable(ht) {}

void TableTennisPlayer::Name() const
{
    std::cout &lt;&lt; lastname &lt;&lt; &quot;, &quot; &lt;&lt; firstname;
}
</code></pre><p>构造函数使用了第12章介绍的初始化列表语法，但也可以像下面这样做：</p>
<pre><code>TableTennisPlayer::TableTennisPlayer(const string &amp; fn,
                    const string &amp; ln, bool ht)
{
    firstname = fn; 
    lastname = ln;
    hasTable = ht; 
}
</code></pre><p>这将首先为firstname调用string的默认构造函数，再调用string的赋值运算符将fitstname设置为fn，但初始化列表语法可以减少一个步骤，它直接使用string的复制构造函数将firstname初始化为fn。</p>
<p>程序13.3 usett0.cpp</p>
<pre><code>#include &lt;iostream&gt;
#include &quot;tabtenn0.h&quot;

int main()
{
    using std::cout;
    TableTennisPlayer player1(&quot;Chuck&quot;, &quot;Blizzard&quot;, true);
    TableTennisPlayer player2(&quot;Tara&quot;, &quot;Boomdea&quot;, false);

    player1.Name();
    if (player1.HasTable())
        cout &lt;&lt; &quot;: has a table.\n&quot;;
    else
        cout &lt;&lt; &quot;: hasn&apos;t a table.\n&quot;;
    player2.Name();
        if (player2.HasTable())
                cout &lt;&lt; &quot;: has a table.\n&quot;;
        else
                cout &lt;&lt; &quot;: hasn&apos;t a table.\n&quot;;

    return 0;
}
</code></pre><p>输出：</p>
<pre><code>Blizzard, Chuck: has a table.
Boomdea, Tara: hasn&apos;t a table.
</code></pre><p>注意到该程序实例化对象将C-风格字符串作为参数：</p>
<pre><code>TableTennisPlayer player1(&quot;Chuck&quot;, &quot;Blizzard&quot;, true);
TableTennisPlayer player2(&quot;Tara&quot;, &quot;Boomdea&quot;, false);
</code></pre><p>但构造函数的形参类型被声明为const string &amp;。这导致类型不匹配，但与第12章创建的String类一样，string类有一个将const char *作为参数的构造函数，使用C-风格字符串初始化string对象，将自动调用这个构造函数。总之，可见string对象或C-风格字符串作为构造函数TableTennisPlayer的参数；将前者作为参数时，将调用接受const string &amp;作为参数的string构造函数，而将后者作为参数，将调用接受const char $\ast$作为参数的string构造函数。</p>
<h3 id="13-1-1-派生一个类"><a href="#13-1-1-派生一个类" class="headerlink" title="13.1.1 派生一个类"></a>13.1.1 派生一个类</h3><p>Webtown俱乐部的一些成员曾经参加过当地的乒乓球锦标赛，需要这样一个类，它能包括成员在比赛中的比分。与其从零开始，不如从TableTennisPlayer类派生出一个类。首先将RatedPlayer类声明为TableTennisPlayer类派生而来的：</p>
<pre><code>class RatedPlayer : public TableTennisPlayer
{
    ...    
};
</code></pre><p>冒号指出RatePlayer类的基类是TableTennisPlayer类。上述特殊的声明头表明TableTennisPlayer是一个公有基类，这被称为公有派生。派生类对象包含基类对象。使用公有派生，基类的公有成员将成为派生类的公有成员；基类的私有部分也将成员派生类的一部分，但只能通过基类的公有和保护方法访问。</p>
<p>RatedPlayer对象具有如下特征：</p>
<ul>
<li>派生类对象存储了基类的数据成员(派生类继承了基类的实现)；</li>
<li>派生类对象可以使用基类的方法(派生类继承了基类的接口)。</li>
</ul>
<p>因此，RatedPlayer对象可以存储运动员的姓名及其是否有球桌。另外，RatedPlayer对象还可以使用TableTennisPlayer类的Name()、hasTable()和ResetTable()方法(见图13.1)。 </p>
<p><img src="https://i.imgur.com/aXeud2q.png" alt=""></p>
<p>需要在继承特性中添加什么呢？</p>
<ul>
<li>派生类需要自己的构造函数。</li>
<li>派生类可以根据需要添加额外的数据成员和成员函数。</li>
</ul>
<p>在这个例子中，派生类需要另一个数据成员来存储比分，还应包含检索比分的方法和重置比分的方法。因此，类声明与下面类似：</p>
<pre><code>class RatedPlayer() : public TableTennisPlyaer
{
private:
    unsigned int rating;
public:
    RatedPlayer(unsigned int r = 0, const string &amp; fn = &quot;none&quot;,
                const string &amp; ln = &quot;none&quot;, bool ht = false);
    RatedPlayer(unsigned int r = 0, const TableTennisPlayer &amp; tp);
    unsigned int Rating() const { return rating; }
    void ResetRating(unsigned int r) { rating = r; }
};
</code></pre><p>构造函数必须给新成员(如果有的话）和继承的成员提供数据。在第一个RatedPlayer构造函数中，每个成员对应一个形参；而第二个RatedPlayer构造函数中使用一个TableTennisPlayer参数，该参数包括firstname, lastname和hasTable。</p>
<h3 id="13-1-2-构造函数：访问权限的考虑"><a href="#13-1-2-构造函数：访问权限的考虑" class="headerlink" title="13.1.2 构造函数：访问权限的考虑"></a>13.1.2 构造函数：访问权限的考虑</h3><p><strong>派生类不能直接访问基类的私有成员，而必须通过基类方法进行访问。</strong>例如，RatePlayer构造函数不能直接设置继承的成员(firstname, lastname和hasTable)，而必须使用基类的公有方法来访问私有的基类成员。具体来说，<strong>派生类构造函数必须使用基类构造函数</strong>。</p>
<p>创建派生对象时，程序首先创建基类对象。从概念上说，<strong>这意味着基类对象应当在程序进入派生类构造函数之前被创建。C++使用成员初始化列表语法来完成这种工作。</strong>例如，下面是第一个RatedPlayer构造函数的代码：</p>
<pre><code>RatedPlayer(unsigned int r = 0, const string &amp; fn = &quot;none&quot;,
                const string &amp; ln = &quot;none&quot;, bool ht = false) : TableTennisPlayer(fn, ln, ht)
{
    rating = r;
}
</code></pre><p>其中 TableTennisPlayer(fn, ln, ht)是成员列表初始化列表。它是可执行的代码，调用TableTennisPlayer构造函数。例如，假设程序包含如下声明：</p>
<pre><code>RatedPlayer rplayer1(1140, &quot;Mallory&quot;, &quot;Duck&quot;, true);
</code></pre><p>则RatedPlayer构造函数将把实参”Mallory”、”Duck”和true赋给形参fn、ln和ht，然后，将这些形参作为实参传递给TableTennisPlayer构造函数，后者将创建一个嵌套TableTennisPlayer对象，并将数据”Mallory”、”Duck”和true存储在对象中。然后，程序进入RatedPlayer构造函数体，完成RatedPlayer对象的创建，并将参数r赋给rating成员。</p>
<p>如果省略成员初始化列表，情况将如何？</p>
<pre><code>RatedPlayer(unsigned int r = 0, const string &amp; fn = &quot;none&quot;,
                const string &amp; ln = &quot;none&quot;, bool ht = false)
{
    rating = r;
}
</code></pre><p>必须首先创建基类对象，如果不调用基类构造函数，程序将使用默认的基类构造函数，因此上述代码与下面等效：、</p>
<pre><code>RatedPlayer(unsigned int r = 0, const string &amp; fn = &quot;none&quot;,
                const string &amp; ln = &quot;none&quot;, bool ht = false) : TableTennisPlayer()
{
    rating = r;
}
</code></pre><p><strong>除非要使用默认构造函数，否则应显式调用正确的基类构造函数。</strong></p>
<p>下面看第二个构造函数的代码：</p>
<pre><code>RatedPlayer(unsigned int r = 0, const TableTennisPlayer &amp; tp) 
                : TableTennisPlayer(tp)
{
    rating = r;
}
</code></pre><p>这里也将TableTennisPlayer的信息传递给了TableTennisPlayer构造函数：    </p>
<pre><code>TableTennisPlayer(tp)
</code></pre><p>由于tp的类型为TableTennisPlayer &amp;，因此将调用基类的复制构造函数。<strong>基类没有定义复制构造函数，但第12章介绍过，如果需要使用复制构造函数但没有定义，编译器将自动生成一个。在这种情况下，执行成员复制的隐式复制构造函数是合适的，因为这个类没有使用动态内存分配</strong>(string成员确实使用了动态内存分配，但本书前面讲过，成员复制将使用string类的复制构造函数来复制string成员)。</p>
<p>如果愿意，也可以对派生类成员使用初始化列表语法。在这种情况下，应在列表中使用成员名，而不是类名。所以，第二个构造函数可以按照下述方式编写：</p>
<pre><code>RatedPlayer(unsigned int r = 0, const TableTennisPlayer &amp; tp) 
                : TableTennisPlayer(tp), rating(r)
{
}
</code></pre><p>有关派生类构造函数的要点如下：</p>
<ul>
<li>首先创建基类对象；</li>
<li>派生类构造函数通过成员初始化列表将基类信息传递给基类构造函数；</li>
<li>派生类构造函数应初始化派生类新增的数据成员。</li>
</ul>
<p>这个例子没有提供显式析构函数，因此将使用隐式析构函数。释放对象的顺序与创建对象顺序相反，即首先执行派生类的析构函数，然后自动调用基类的析构函数。</p>
<p><strong>注意:</strong> 创建派生类对象时，程序首先调用基类的构造函数，然后再调用派生类的构造函数。基类构造函数负责初始化继承的数据成员；派生类构造函数负责初始化新增的数据成员。派生类的构造函数总是调用一个基类的构造函数。可以使用初始化列表语法知名要使用的基类构造函数，否则使用默认的基类构造函数。</p>
<center><strong>成员初始化列表</strong></center>

<p>派生类构造函数可以使用初始化列表机制将值传递给基类构造函数。请看下面的例子：</p>
<pre><code>derived::derived(type1 x, type2 y)::base(x, y)
{
    ...
}
</code></pre><p>其中derived是派生类，base是基类，x和y是基类构造函数使用的变量。例如，如果派生类构造函数接收到参数10和12，则这种机制将把10和12传递给被定义为接受这些类型参数的基类构造函数。除虚基类外(第14章)，类只能将值传递给相邻的基类，方后者可以使用相同的机制将信息传递给相邻的基类，依此类推。</p>
<h3 id="13-1-3-使用派生类"><a href="#13-1-3-使用派生类" class="headerlink" title="13.1.3 使用派生类"></a>13.1.3 使用派生类</h3><p>要使用派生类，程序必须要能够访问基类声明。程序13.4将这两个类置于同一头文件中，也可以将每个类放在独立的头文件中，但这两个类是相关的，所以把其类声明放在一起更合适。</p>
<p>程序13.4 tabtenn1.h</p>
<pre><code>#ifndef TABTENN1_H_
#define TABTENN1_H_
#include &lt;string&gt;
using std::string;
class TableTennisPlayer
{
private:
    string firstname;
    string lastname;
    bool hasTable;
public:
    TableTennisPlayer(const string &amp; fn = &quot;none&quot;,
              const string &amp; ln = &quot;none&quot;,
              bool ht = false);
    void Name() const;
    bool HasTable() const {return hasTable;}
    void ResetTable(bool v) {hasTable = v;}
};

class RatedPlayer : public TableTennisPlayer
{
private:
    unsigned int rating;
public:
    RatedPlayer(unsigned int r = 0, const string &amp; fn = &quot;none&quot;,
            const string &amp; ln = &quot;none&quot;, bool ht = false);
    RatedPlayer(unsigned int r, const TableTennisPlayer &amp; pt);
    unsigned int Rating() const { return rating; }
    void ResetRating(unsigned int r) { rating = r; }
};

#endif
</code></pre><p>程序13.5 tabtenn1.cpp</p>
<pre><code>#include &lt;iostream&gt;
#include &quot;tabtenn1.h&quot;

TableTennisPlayer::TableTennisPlayer(const string &amp; fn,
    const string &amp; ln, bool ht):firstname(fn), 
        lastname(ln),hasTable(ht) {}

void TableTennisPlayer::Name() const
{
    std::cout &lt;&lt; lastname &lt;&lt; &quot;, &quot; &lt;&lt; firstname;
}

RatedPlayer::RatedPlayer(unsigned int r, const string &amp; fn,
             const string &amp; ln, bool ht) : TableTennisPlayer(fn, ln, ht)
{
    rating = r;
}

RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer &amp; pt)
    : TableTennisPlayer(pt), rating(r)
{
}
</code></pre><p>程序13.6 usett1.cpp</p>
<pre><code>#include &lt;iostream&gt;
#include &quot;tabtenn1.h&quot;

int main()
{
    using std::cout;
    using std::endl;
    TableTennisPlayer player1(&quot;Tara&quot;, &quot;Boomdea&quot;, false);
    RatedPlayer rplayer1(1140, &quot;Mallory&quot;, &quot;Duck&quot;, true);
    rplayer1.Name();
    if (rplayer1.HasTable())
        cout &lt;&lt; &quot;: has a table.\n&quot;;
    else
        cout &lt;&lt; &quot;: hasn&apos;t a table.\n&quot;;
    player1.Name();
    if (player1.HasTable())
                cout &lt;&lt; &quot;: has a table.\n&quot;;
        else
                cout &lt;&lt; &quot;: hasn&apos;t a table.\n&quot;;
    cout &lt;&lt; &quot;Name: &quot;;
    rplayer1.Name();
    cout &lt;&lt; &quot;; Rating: &quot; &lt;&lt; rplayer1.Rating() &lt;&lt; endl;

    RatedPlayer rplayer2(1212, player1);
    cout &lt;&lt; &quot;Name: &quot;;
    rplayer2.Name();
        cout &lt;&lt; &quot;; Rating: &quot; &lt;&lt; rplayer2.Rating() &lt;&lt; endl; 
    return 0;
}
</code></pre><p>输出：</p>
<pre><code>Duck, Mallory: has a table.
Boomdea, Tara: hasn&apos;t a table.
Name: Duck, Mallory; Rating: 1140
Name: Boomdea, Tara; Rating: 1212
</code></pre><h3 id="13-1-4-派生类和基类之间的特殊关系"><a href="#13-1-4-派生类和基类之间的特殊关系" class="headerlink" title="13.1.4 派生类和基类之间的特殊关系"></a>13.1.4 派生类和基类之间的特殊关系</h3><p>派生类与基类之间有一些特殊关系。其中之一是派生类对象可以使用基类的方法，条件是方法不是私有的：</p>
<pre><code>RatedPlayer rplayer(1140, &quot;Mallory&quot;, &quot;Duck&quot;, true);
rplayer.Name();
</code></pre><p>另外两个重要的关系是：基类指针可以在不进行显示类型转换的情况下指向派生类对象；基类引用可以在不进行显式类型转换的情况下引用派生对象：</p>
<pre><code>RatedPlayer rplayer1(1140, &quot;Mallory&quot;, &quot;Duck&quot;, true);
TableTennisPlayer &amp; rt = rplayer;
TableTennisPlayer * pt = &amp;rplayer;
rt.Name();
rt-&gt;Name();
</code></pre><p>然而，基类指针或引用只能用于调用基类方法，因此，不能使用rt或pt调用派生类的ResetRating方法。</p>
<p>通常，C++要求引用和指针类型与赋给的类型匹配，但这一规则对继承来说是例外。然而，这种例外只是单向的，不可以将基类对象和地址赋给派生类应用和指针。</p>
<p>上述规则是有道理的。例如，如允许基类引用隐式地派生类对象，则可以使用基类引用派生类对象调用基类的方法。因为派生类继承了基类的方法，所以这样做不会出现问题。如果可以将基类对象赋给派生类引用，将发生什么情况呢？派生类引用能够为基对象调用派生类方法，这样做将出现问题。例如，将RatedPlayer::Rating()方法用于TableTennisPlayer对象是没有意义的，因为TableTennisPlayer对象没有rating成员。</p>
<p>如果基类引用和指针指向派生类对象，将出现一些有趣的结果。其中之一是基类引用定义的函数或参数可用基类对象或派生对象。例如，下面的函数中：</p>
<pre><code>void Show(cosnt TableTennisPlayer &amp; rt)
{
    using std::cout;
    cout &lt;&lt; &quot;Name: &quot;;
    rt.Name();
    cout &lt;&lt; &quot;\nTable: &quot;;
    if (rt.HasTable())
        cout &lt;&lt; &quot;yes\n&quot;;
    else
        cout &lt;&lt; &quot;no\n&quot;;
}
</code></pre><p>形参rt是<strong>一个基类的引用，它可以指向基类对象或派生类对象</strong>，所以可以在Show()中使用TableTennis参数或RatedPlayer参数：</p>
<pre><code>TableTennisPlayer player1(&quot;Tara&quot;, &quot;Boomdea&quot;, false);
RatedPlayer rplayer1(1140, &quot;Mallory&quot;, &quot;Duck&quot;, true);
Show(player1);
Show(rplayer1);
</code></pre><p><strong>对于形参为指向基类的指针的函数，也存在相似的关系。它可以使用基类对象的地址或派生类对象的地址作为实参</strong>：</p>
<pre><code>void Wohs(const TableTennisPlayer * pt);

TableTennisPlayer player1(&quot;Tara&quot;, &quot;Boomdea&quot;, false);
RatedPlayer rplayer1(1140, &quot;Mallory&quot;, &quot;Duck&quot;, true);
Wohs(&amp;player1);
Wohs(&amp;rplayer1);
</code></pre><p>引用兼容属性也让您<strong>能够将基类对象初始化为派生类对象</strong>，尽管不那么直接。假设有这样的代码：</p>
<pre><code>RatedPlayer olaf1(1840, &quot;Olaf&quot;, &quot;Loaf&quot;, true);
TableTennisPlayer olaf2(olaf1);
</code></pre><p>要初始化olaf，匹配的构造函数的原型如下：</p>
<pre><code>TableTennisPlayer(const RatedPlayer &amp;); //没有定义
</code></pre><p>类定义中没有这样的构造函数，但存在隐式复制构造函数：</p>
<pre><code>TableTennisPlayer(const TableTennisPlayer &amp;);
</code></pre><p>形参是基类的引用，因此它可以引用派生类。这样，将olaf2初始化olaf1时，将要使用该构造函数，它复制firstname、lastname和hasTable成员。即，它将olaf2初始化为嵌套在RatedPlayer对象olaf1中的TableTennisPlayer对象。</p>
<p>同样，<strong>也可以将派生对象赋给基类对象</strong>：</p>
<pre><code>RatedPlayer olaf1(1840, &quot;Olaf&quot;, &quot;Loaf&quot;, true);
TableTennisPlayer winner;
winner = olaf1;
</code></pre><p>在这种情况下，程序将使用隐式重载复制运算符：</p>
<pre><code>TableTennisPlayer &amp; operator=(const TableTennisPlayer &amp;) const;
</code></pre><p>基类引用指向也是派生类对象，因此olaf1的基类部分被复制给winner。</p>
<h2 id="13-2-继承：is-a关系"><a href="#13-2-继承：is-a关系" class="headerlink" title="13.2 继承：is-a关系"></a>13.2 继承：is-a关系</h2><p>派生类和基类之间的特殊关系是基于C++继承的底层模型。实际上，C++有3种继承方式：公有继承、保护继承和私有继承。公有继承是最常用的方式，它建立一种is-a关系，即派生类对象也是一个基类对象，可以对基类对象执行的任何对象，也可以对派生类执行。因为派生类可以添加特性，所以，将这种关系成为is-a-kind-of(是一种)关系可能更准确，但通常使用属于is-a。</p>
<p>为阐明is-a关系，来看一些与该模型不符的例子。公有继承不建立is-a关系。例如，午餐可能包括水果，但通常午餐并不是水果。所以，不能通过从Fruit派生出Lunch类来在午餐中添加水果。在午餐中加入水果的正确做法是将其作为一种has-a关系：午餐有水果。正如将在第14章介绍的，最容易的建模方式是，将Fruit对象作为Lunch类的数据成员。参见图13.3。</p>
<p><img src="https://i.imgur.com/9rsExmz.png" alt=""></p>
<p>公有继承不能建立is-like-a关系，即，它不采用明喻。人们通常说律师像鲨鱼，但律师并不是鲨鱼。例如，鲨鱼可以在水下生活。所以，不应从Shark类派生出Lawyer类。<strong>继承可以在基类的基础上添加属性，但不能删除基类的属性。</strong>在有些情况下，可以设计一个包含共有特征的类，然后，以is-a或has-a关系，在这个类的基础上定义相关的类。</p>
<p>公有类继承不讲理is-implemented-as-a(作为……来实现)关系。例如，可以使用数组来实现栈，但从Array类派生出Stack类是不合适的，因为栈不是数组。例如，数组索引不是栈的属性。另外，可以以其他方式实现栈，如链表。正确的做法是，通常让栈包含一个私有Array对象来隐藏数组实现。</p>
<p>公有继承不见uses-a关系。例如，计算机可以使用激光打印机，但从computer类派生出Priter类(或反过来)是没有意义的。然而，可以使用友元函数或类来处理Printer对象和Computer对象之间的通信。</p>
<p>在C++中，完全可以使用公有继承来建立has-a、is-implemented-as-a或uses-a关系；然而，这样做通常导致编程方面的问题。因此，还是坚持使用is-a关系吧。</p>
<h2 id="13-3-多态公有继承"><a href="#13-3-多态公有继承" class="headerlink" title="13.3 多态公有继承"></a>13.3 多态公有继承</h2><p>RatedPlayer继承示例很简单。派生类对象使用基类对象的方法，而未做任何修改。然而，可能会遇到这样的情况，即希望同一个方法在派生类和基类中的行为是不同的。即，方法的行为应取决于调用该方法的对象。这种较复杂的行为称为多态—具有多种形参，即同一个方法的行为随上下文而异。有两种重要的机制可用于实现多态公有继承：</p>
<ul>
<li>在派生类中重新定义基类的方法。</li>
<li>使用虚方法。</li>
</ul>
<p>现在来看一个例子。由于Webtown俱乐部的工作经历，您成了Pontoon银行的首席程序员。银行要求您完成的第一项开发工作是开发两个类。一个类用于表示基本支票账户—Brass Acccount，另一个类用于表示Brass Plus支票账户，它添加了透支保护特性。即，如果用户签出一张超出其存储余额的支票—但是超出的数额并不是很大，银行将支付这张支票，对超出的部分收取额外的费用，并追加罚款。可以根据要保存的数据以及允许执行的操作来确定这两种账户的特征。</p>
<p>下面是用于Brass Account支票账户的信息：</p>
<ul>
<li>客户姓名</li>
<li>账户</li>
<li>当前结余</li>
</ul>
<p>下面是可以执行的操作：</p>
<ul>
<li>创建账户</li>
<li>存款</li>
<li>取款</li>
<li>显式账户信息</li>
</ul>
<p>Pontoon银行希望Brass Plus支票账户包含Brass Account的所有信息及如下信息：</p>
<ul>
<li>透支上限</li>
<li>透支贷款利率</li>
<li>当前的透支总额</li>
</ul>
<p>不需要新增操作，但有两种操作的实现不同：</p>
<ul>
<li>对于取款操作，必须考虑透支保护；</li>
<li>显式操作必须显式Brass Plus账户的其他信息。</li>
</ul>
<p>假设将第一个类命名为Brass，第二个类为BrassPlus。应从Brass公有派生出BrassPlus吗？要回答这个问题，必须先回答另一个问题：BrassPlus类是否满足is-a条件？当然满足。对于Brass对象是正确的事情，对于BrassPlus对象也是正确的。它们都将保存客户姓名、账户以及余额。使用这两个类都可以存款、取款和显式账户信息。请注意，is-a关系通常是不可逆的。也就是说，水果不是香蕉；同样，Brass对象不具备BrassPlus对象的所有功能。</p>
<h3 id="13-3-1-开发Brass类和BrassPlus类"><a href="#13-3-1-开发Brass类和BrassPlus类" class="headerlink" title="13.3.1 开发Brass类和BrassPlus类"></a>13.3.1 开发Brass类和BrassPlus类</h3><p>Brass Account类的信息很简单，但是银行没有告诉您有关透支系统的细节。仅提供了如下信息：</p>
<ul>
<li>Brass Plus账户限制了客户的透支额度。默认为500元，但有些客户的额度可能不同；</li>
<li>银行可以修改客户的透支限额；</li>
<li>Brass Plus账户对贷款收取利息。默认为11.125%，但有些客户的利率可能不同；</li>
<li>银行可以修改客户的利率；</li>
<li>银行记录客户所欠银行的金额(透支数额加利息)。用户不能通过常规存款或从其他账户转账的方式偿付，而必须以现金的方式交给特定的银行工作人员。如果有必要，工作人员可以找到该客户。欠款还清后，欠款金额归零。</li>
</ul>
<p>最后一种特性是银行处于做生意的考虑而采用的，这种方法有它有利的一面—-使编程更简单。</p>
<p>上述列表表明，新的类需要构造函数，而且构造函数应提供账户信息，设置透支上限和利率。另外，还应有重新设置透支限额、利率和当前欠款的方法。要添加到Brass类中的就这些信息，这将在Brass Plus类声明中声明。</p>
<p>有关这两个类的信息声明，类声明应类似于程序13.7。</p>
<p>程序13.7 brass.h</p>
<pre><code>#ifndef BRASS_H_
#define BRASS_H_
#include &lt;string&gt;
class Brass
{
private:
    std::string fullName;
    long acctNum;
    double balance;
public:
    Brass(const std::string &amp; s = &quot;Nullbody&quot;, long an = -1,
        double bal = 0.0);
    void Deposit(double amt);
    virtual void Withdraw(double amt);
    double Balance() const;
    virtual void ViewAcct() const;
    virtual ~Brass() {}
};

class BrassPlus : public Brass
{
private:
    double maxLoan;
    double rate;
    double owesBank;
public:
    BrassPlus(const std::string &amp; s = &quot;Null&quot;, long an = -1,
          double bal = 0.0, double ml = 500,
          double r = 0.11125);
    BrassPlus(const Brass &amp; ba, double ml = 500,
                    double r = 0.11125);
    virtual void ViewAcct() const;
    virtual void Withdraw(double amt);
    void ResetMax(double m) { maxLoan = m; }
    void ResetRate(double r) { rate = r; }
    void ResetOwes() { owesBank = 0; }
};

#endif
</code></pre><p>对于程序13.7，需要说明如下几点：</p>
<ul>
<li>BrassPlus类在Brass类的基础上添加了3个私有成员和3个公有成员函数；</li>
<li>Brass类和BrassPlus类都声明了ViewAcct()和Withdraw()方法，但BrassPlus对象和Brass对象的这些方法的行为是不同的；</li>
<li>Brass类声明ViewAcct()和Withdraw()使用了新关键字vitual，这些方法被称为<strong>虚方法</strong>；</li>
<li>Brass类还声明了一个虚析构函数，虽然该析构函数不执行任何操作。</li>
</ul>
<p>第二点，介绍了声明如何指出方法在派生类的行为的不同。两个ViewAcct()原型表明将有2个独立的方法定义。基类版本的限定为Brass::ViewAcct()，派生类版本的限定名为BrassPlus::ViewAcct()。程序将使用对象类型来确定使用哪个版本：</p>
<pre><code>Brass dom(&quot;Porcelot Pigg&quot;, 381299, 4000.00);
BrassPlus dot(&quot;Horatio Hogg&quot;, 382288, 3000.00);

dom.ViewAcct();
dot.ViewAcct();
</code></pre><p>第三点，如果方法是通过引用或指针而不是对象调用的，它将确定使用哪一种方法。如果没有使用关键字vitual，程序将根据引用类型或指针类型选择方法；如果使用了virtua，程序将根据引用或指针指向的对象的类型来选择方法。如果ViewAcct()不是虚函数，则程序的行为如下：</p>
<pre><code>Brass dom(&quot;Porcelot Pigg&quot;, 381299, 4000.00);
BrassPlus dot(&quot;Horatio Hogg&quot;, 382288, 3000.00);
Brass &amp; b1_ref = dom;
Brass &amp; b2_ref = dot;
b1_ref.ViewAcct();
b2_ref.ViewAcct();
</code></pre><p>引用变量的类型为Brass，所以都选择了Brass::ViewAccount()。使用Brass指针代替引用时，行为将与此类似。</p>
<p>如果ViewAcct()是虚函数，则行为如下：</p>
<pre><code>Brass dom(&quot;Porcelot Pigg&quot;, 381299, 4000.00);
BrassPlus dot(&quot;Horatio Hogg&quot;, 382288, 3000.00);
Brass &amp; b1_ref = dom;
Brass &amp; b2_ref = dot;
b1_ref.ViewAcct();
b2_ref.ViewAcct();
</code></pre><p>这里两个引用的类型都是Brass，但b2_ref引用的是一个BrassPlus对象，所以使用的是BrassPlus::ViewAcct()。使用Brass指针代替引用时，行为将类似。</p>
<p>稍后会发现，虚函数的这种行为非常方便。因此，经常在基类中将派生类会重新定义的方法声明为虚方法。方法在基类中被声明为虚方法后，它在派生类中将自动变成虚方法。然而，在派生类中声明中使用关键字virtual来指出哪些方法是虚方法也是一个好办法。</p>
<p>第四点，基类中声明了一个虚析构函数。这样做是为了确保释放派生对象时，按正确的顺序调用析构函数。本章后面将介绍这个问题。</p>
<p><strong>注意：</strong>如果要在派生类中重新定义基类的方法，通常应将基类方法声明为虚方法。这样，程序将根据对象类型而不是引用或指针的类型来选择方法版本。<strong>为基类声明一个虚析构函数也是一种惯例。</strong></p>
<p><strong>1. 类实现</strong></p>
<p>关键字virtual只用于类声明的方法原型中。</p>
<p>程序13.8 brass.cpp</p>
<pre><code>#include &quot;brass.h&quot;
#include &lt;iostream&gt;
#include &lt;string&gt;
using namespace std;

typedef std::ios_base::fmtflags format;
typedef std::streamsize precis;
format setFormat();
void restore(format f, precis p);

Brass::Brass(const string &amp; s, long an, double bal)
{
    fullName = s;
    acctNum = an;
    balance = bal;
}

void Brass::Deposit(double amt)
{
    if (amt &lt; 0)
        cout &lt;&lt; &quot;Negative deposit not allowed; &quot;
             &lt;&lt; &quot;deposit is cancelled.\n&quot;;
    else
        balance += amt;
}

void Brass::Withdraw(double amt)
{
    format initialState = setFormat();
    precis prec = cout.precision(2);

    if (amt &lt; 0)
        cout &lt;&lt; &quot;Withdrawal amount must be positive; &quot;
             &lt;&lt; &quot;withdrawal canceled.\n&quot;;
    else if (amt &lt;= balance)
        balance -= amt;
    else
        cout &lt;&lt; &quot;Withdrawal amount of $&quot; &lt;&lt; amt
             &lt;&lt; &quot; exceeds your balance.\n&quot;
             &lt;&lt; &quot;Withdrawal canceled.\n&quot;;
    restore(initialState, prec);
}

double Brass::Balance() const
{
    return balance;
}

void Brass::ViewAcct() const
{
    format initialState = setFormat();
    precis prec = cout.precision(2);
    cout &lt;&lt; &quot;Client: &quot; &lt;&lt; fullName &lt;&lt; endl;
    cout &lt;&lt; &quot;Account Number: &quot; &lt;&lt; acctNum &lt;&lt; endl;
    cout &lt;&lt; &quot;Balance: $&quot; &lt;&lt; balance &lt;&lt; endl;
    restore(initialState, prec);
}

BrassPlus::BrassPlus(const string &amp; s, long an, double bal,
             double ml, double r): Brass(s, an, bal)
{
    maxLoan = ml;
    owesBank = 0.0;
    rate = r;
}

BrassPlus::BrassPlus(const Brass &amp; ba, double ml, double r)
        : Brass(ba)
{
    maxLoan = ml;
    owesBank = 0.0;
    rate = r;
}

void BrassPlus::ViewAcct() const
{
    format initialState = setFormat();
    precis prec = cout.precision(2);

    Brass::ViewAcct();
    cout &lt;&lt; &quot;Maximum loan: $&quot; &lt;&lt; maxLoan &lt;&lt; endl;
    cout &lt;&lt; &quot;Owed to bank: $&quot; &lt;&lt; owesBank &lt;&lt; endl;
    cout.precision(3);
    cout &lt;&lt; &quot;Loan Rate: &quot; &lt;&lt; 100 * rate &lt;&lt; &quot;%\n&quot;;
    restore(initialState, prec);
}

void BrassPlus::Withdraw(double amt)
{
    format initialState = setFormat();
    precis prec = cout.precision(2);

    double bal = Balance();
    if (amt &lt;= bal)
        Brass::Withdraw(amt);
    else if ( amt &lt;= bal + maxLoan - owesBank)
    {
        double advance = amt - bal;
        owesBank += advance * (1.0 + rate);
        cout &lt;&lt; &quot;Bank advance: $&quot; &lt;&lt; advance &lt;&lt; endl;
        cout &lt;&lt; &quot;Finance charge: $&quot; &lt;&lt; advance * rate &lt;&lt; endl;
        Deposit(advance);
        Brass::Withdraw(amt);
    }
    else
        cout &lt;&lt; &quot;Credit limit exceeded. Transaction cancelled.\n&quot;;
    restore(initialState, prec);
}

format setFormat()
{
    return cout.setf(ios_base::fixed,ios_base::floatfield);
}

void restore(format f, precis p)
{
    cout.setf(f, ios_base::floatfield);
    cout.precision(p);
}
</code></pre><p>首先，看一下与继承相关的方面。记住，派生类并不能访问基类的私有成员，而必须通常使用基类的公有方法才能访问这些数据。访问的方式取决于方法。构造函数使用一种技术，而其他成员函数使用另一种技术u</p>
<p>派生构造函数在初始化基类私有数据时，采用的是成员初始化列表语法。RatedPlayer和BrassPlus构造函数都使用了这种技术:</p>
<pre><code>BrassPlus::BrassPlus(const string &amp; s, long an, double bal,
             double ml, double r): Brass(s, an, bal)
{
    maxLoan = ml;
    owesBank = 0.0;
    rate = r;
}

BrassPlus::BrassPlus(const Brass &amp; ba, double ml, double r)
        : Brass(ba)
{
    maxLoan = ml;
    owesBank = 0.0;
    rate = r;
}
</code></pre><p>这几个构造函数都使用成员初始化列表语法，将基类信息传递给基类构造函数，然后使用构造函数体初始化BrassPlus类新增的数据成员。</p>
<p>非构造函数不能使用成员初始化类别语法，但派生类可以调用公有的基类方法。例如，BrassPlus版本的ViewAcct()核心内容如下:</p>
<pre><code>void BrassPlus::ViewAcct() const
{
...    
    Brass::ViewAcct();
    cout &lt;&lt; &quot;Maximum loan: $&quot; &lt;&lt; maxLoan &lt;&lt; endl;
    cout &lt;&lt; &quot;Owed to bank: $&quot; &lt;&lt; owesBank &lt;&lt; endl;
    cout &lt;&lt; &quot;Loan Rate: &quot; &lt;&lt; 100 * rate &lt;&lt; &quot;%\n&quot;;
...
}
</code></pre><p>换句话说，BrassPlus::ViewAcct()显式新增的BrassPlus数据成员，并调用基类方法Brass::ViewAcct()来显示基类数据成员。在派生类方法中，标准技术是使用作用域解析运算符来调用基类方法。</p>
<p>如果代码没有使用作用域解析运算符，编译器将认为ViewAcct()是BrassPlus::ViewAcct()，这将创建一个不会终止的递归函数—这可不好。</p>
<p>接下来看BrassPlus::Withdraw()方法。如果客户提取的金额超过了结余，该方法将安排贷款。它可以使用Brass::Withdraw()来访问balance成员，但如果提取的金额超过了结余，Brass::Withdraw()将发出一个错误消息。这种实现使用Deposit()进行放贷，然后在得到了足够的结余后，调用Brass::Withdraw()，从而避免了错误信息。</p>
<pre><code>void BrassPlus::Withdraw(double amt)
{
...
    double bal = Balance();
    if (amt &lt;= bal)
        Brass::Withdraw(amt);
    else if ( amt &lt;= bal + maxLoan - owesBank)
    {
        double advance = amt - bal;
        owesBank += advance * (1.0 + rate);
        cout &lt;&lt; &quot;Bank advance: $&quot; &lt;&lt; advance &lt;&lt; endl;
        cout &lt;&lt; &quot;Finance charge: $&quot; &lt;&lt; advance * rate &lt;&lt; endl;
        Deposit(advance);
        Brass::Withdraw(amt);
    }
    else
        cout &lt;&lt; &quot;Credit limit exceeded. Transaction cancelled.\n&quot;;
...
}
</code></pre><p>该方法使用基类的Balance()函数来确定结余。因为派生类没有重新定义该方法，代码不必对Balance()使用作用域解析运算符。</p>
<p>方法ViewAcct()和Withdraw()使用格式化方法setf()和precision()将浮点值的输出模式设置为定点，即包含两位小数。设置模式后，输出的模式将保持不变，因此该方法将格式模式重置为调用前的模式。这与程序8.8和程序10.5类似。为避免代码重复，该程序将设置格式的代码放在辅助函数中：</p>
<pre><code>typedef std::ios_base::fmtflags format;
typedef std::streamsize precis;
format setFormat();
void restore(format f, precis p);
</code></pre><p>函数setFormat()设置定点表示法并返回以前的标记设置：</p>
<pre><code>format setFormat()
{
    return cout.setf(ios_base::fixed,ios_base::floatfield);
}
</code></pre><p>而函数restore()重置格式和进度：</p>
<pre><code>void restore(format f, precis p)
{
    cout.setf(f, ios_base::floatfield);
    cout.precision(p);
}
</code></pre><p>有关设置输出格式的更详细信息，参阅第17章。</p>
<p><strong>2. 使用Brass和BrassPlus类</strong></p>
<p>程序13.9 usebrass1.cpp</p>
<pre><code>#include &quot;brass.h&quot;
#include &lt;iostream&gt;

int main()
{
    using std::cout;
    using std::endl;

    Brass Piggy(&quot;Porcelot Pigg&quot;, 381299, 4000.00);
    BrassPlus Hoggy(&quot;Horatio Hogg&quot;, 382288, 3000.00);
    Piggy.ViewAcct();
    cout &lt;&lt; endl;
    Hoggy.ViewAcct();
    cout &lt;&lt; endl;
    cout &lt;&lt; &quot;Depositing $1000 into the Hogg Account:\n&quot;;
    Hoggy.Deposit(1000.00);
    cout &lt;&lt; &quot;New balance: $&quot; &lt;&lt; Hoggy.Balance() &lt;&lt; endl;
    cout &lt;&lt; &quot;Withdrawing $4200 from the pigg Account:\n&quot;;
    Piggy.Withdraw(4200.00);
    cout &lt;&lt; &quot;Pigg account balance: $&quot; &lt;&lt; Piggy.Balance() &lt;&lt; endl;
    cout &lt;&lt; &quot;Withdrawing $4200 from the Hogg Account:\n&quot;;
    Hoggy.Withdraw(4200.00);
    Hoggy.ViewAcct();
    return 0;
}
</code></pre><p>输出：</p>
<pre><code>Client: Porcelot Pigg
Account Number: 381299
Balance: $4000.00

Client: Horatio Hogg
Account Number: 382288
Balance: $3000.00
Maximum loan: $500.00
Owed to bank: $0.00
Loan Rate: 11.125%

Depositing $1000 into the Hogg Account:
New balance: $4000
Withdrawing $4200 from the pigg Account:
Withdrawal amount of $4200.00 exceeds your balance.
Withdrawal canceled.
Pigg account balance: $4000
Withdrawing $4200 from the Hogg Account:
Bank advance: $200.00
Finance charge: $22.25
Client: Horatio Hogg
Account Number: 382288
Balance: $0.00
Maximum loan: $500.00
Owed to bank: $222.25
Loan Rate: 11.125%
</code></pre><p><strong>3. 演示虚方法的行为</strong></p>
<p>在程序13.9中，方法是通过对象调用的，没有使用虚方法特性。下面来看一个使用了虚方法的例子。假设要同时管理Brass和BrassPlud账户，如果能使用同一个数组来保存Brass和BrassPlus对象，将很有帮助，但这是不可能的。数组中所有元素的类型必须相同，而Brass和BrassPlus是不同的类型。然而，可以创建指向Brass的指针数组。这样，每个元素的类型都是相同的，但由于使用的是公有继承模型，因此Brass指针既可以指向Brass对象，也可以指向BrassPlus对象。因此，可以使用一个数组来表示多种类型的对象。这就是多态。</p>
<p>程序13.10 usebrass2.cpp</p>
<pre><code>#include &lt;iostream&gt;
#include &lt;string&gt;
#include &quot;brass.h&quot;
using namespace std;
const int CLIENTS = 4;
int main()
{
    Brass * p_clients[CLIENTS];
    string temp;
    long tempnum;
    double tempbal;
    char kind;
    for (int i = 0; i &lt; CLIENTS; i++)
    {
        cout &lt;&lt; &quot;Enter client&apos;s name: &quot;;    
        getline(cin, temp);
        cout &lt;&lt; &quot;Enter client&apos;s account number: &quot;;
        cin &gt;&gt; tempnum;
        cout &lt;&lt; &quot;Enter opening balance: $&quot;;
        cin &gt;&gt; tempbal;
        cout &lt;&lt; &quot;Enter 1 for Brass Account or &quot;
             &lt;&lt; &quot;2 for BrassPlus Account: &quot;;
        while (cin &gt;&gt; kind &amp;&amp; (kind != &apos;1&apos; &amp;&amp; kind != &apos;2&apos;))
            cout &lt;&lt; &quot;Enter either 1 or 2: &quot;;
        if (kind == &apos;1&apos;)
            p_clients[i] = new Brass(temp, tempnum, tempbal);
        else
        {
            double tmax, trate;
            cout &lt;&lt; &quot;Enter the overdraft limit: $&quot;;
            cin &gt;&gt; tmax;
            cout &lt;&lt; &quot;Enter the interest rate &quot;
                 &lt;&lt; &quot;as a decimal fraction: &quot;;
            cin&gt;&gt; trate;
            p_clients[i] = new BrassPlus(temp, tempnum, tempbal, 
                            tmax, trate);
        }
        while (cin.get() != &apos;\n&apos;)
            continue;
    }
    cout &lt;&lt; endl;
    for (int i = 0; i &lt; CLIENTS; i++)
    {
        p_clients[i]-&gt;ViewAcct();
        cout &lt;&lt; endl;
    }

    for (int i = 0; i &lt; CLIENTS; i++)
    {
        delete p_clients[i];
    }
    cout &lt;&lt; &quot;Done!\n&quot;;

    return 0;
}
</code></pre><p>程序13.10根据用户输入来确定要添加的账户类型，然后使用new创建并初始化相应类型的对象。getline(cin, temp)从cin读取一行输入，并将其存储到string对象temp中。</p>
<p>输出：</p>
<pre><code>Enter client&apos;s name: Harry
Enter client&apos;s account number: 112233
Enter opening balance: $1500
Enter 1 for Brass Account or 2 for BrassPlus Account: 1
Enter client&apos;s name: Dinah
Enter client&apos;s account number: 121213
Enter opening balance: $1800
Enter 1 for Brass Account or 2 for BrassPlus Account: 2
Enter the overdraft limit: $350
Enter the interest rate as a decimal fraction: 0.12
Enter client&apos;s name: Brenda
Enter client&apos;s account number: 212118
Enter opening balance: $5200
Enter 1 for Brass Account or 2 for BrassPlus Account: 2
Enter the overdraft limit: $800
Enter the interest rate as a decimal fraction: 0.10
Enter client&apos;s name: Tim 
Enter client&apos;s account number: 233255
Enter opening balance: $688
Enter 1 for Brass Account or 2 for BrassPlus Account: 1

Client: Harry
Account Number: 112233
Balance: $1500.00

Client: Dinah
Account Number: 121213
Balance: $1800.00
Maximum loan: $350.00
Owed to bank: $0.00
Loan Rate: 12.000%

Client: Brenda
Account Number: 212118
Balance: $5200.00
Maximum loan: $800.00
Owed to bank: $0.00
Loan Rate: 10.000%

Client: Tim
Account Number: 233255
Balance: $688.00

Done!
</code></pre><p>多态性是由下述代码提供：</p>
<pre><code>for (int i = 0; i &lt; CLIENTS; i++)
{
    p_clients[i]-&gt;ViewAcct();
    cout &lt;&lt; endl;
}
</code></pre><p>如果书谁说成员指向的是Brass对象，则调用Brass::ViewAcct()；如果指向的是BrassPlus对象，则调用BrassPlus::ViewAcct()。如果Brass::ViewAcct()没有被声明为虚的，则在任何情况下都将调用Brass::ViewAcct()。</p>
<p><strong>4. 为何需要虚析构函数</strong></p>
<p>在程序13.10中，使用delete释放由new分配的对象的代码说明了为何基类应包含一个虚析构函数，虽然有时好像并不需要析构函数。如果析构函数不是虚的，则将调用对应于指针类型的析构函数。对于程序13.10，这意味着只有Brass的析构函数被调用，即使指针指向的是BrassPlus对象。如果析构函数是虚的，将调用相应对象类型的析构函数。因此，如果指针指向的是BrassPlus对象，将调用BrassPlus的析构函数，然后自动调用基类的析构函数。</p>
<p>因此十三亿虚析构函数可以确保正确的析构函数序列被调用。对于程序13.10，这种正确的行为并不重要，因为析构函数没有任何操作。然而，如果BrassPlus包含一个执行某些操作的析构函数，则Brass必须有一个虚析构函数，即使该析构函数不执行任何操作。</p>
<h2 id="13-4-静态联编和动态联编"><a href="#13-4-静态联编和动态联编" class="headerlink" title="13.4 静态联编和动态联编"></a>13.4 静态联编和动态联编</h2><p>程序调用函数时，将使用哪个可执行代码块呢？编译器将负责回答这个问题。<strong>将源代码中的函数调用解释为执行特定的函数代码块被称为函数名联编（binding）。</strong>在C语言中，这非常简单，因为每个函数名都对应一个不同的函数。在C++中，由于函数重载的缘故，这项任务更复杂。编译器必须查看函数参数以及函数名才能确定使用哪个函数。然而，C/C++编译器可以在编译过程中完成这种联编。<strong>在编译过程中进行联编被称为静态联编，又称为早期联编</strong>。然而，虚函数使这项工作变得更困难。正如程序13.10所示的那样，使用哪一个函数不能在编译时确定的，因为编译器不知道用户将选择哪种类型。所以，<strong>编译器必须生成能够在程序运行时选择正确的虚函数的代码，这被称为动态联编，又称为晚期联编。</strong></p>
<p>知道虚方法的行为后，下面深入地探讨这一过程，首先介绍C++如何处理指针和引用类型的兼容性。</p>
<h3 id="13-4-1-指针和引用类型的兼容性"><a href="#13-4-1-指针和引用类型的兼容性" class="headerlink" title="13.4.1 指针和引用类型的兼容性"></a>13.4.1 指针和引用类型的兼容性</h3><p>在C++中，动态联编与通过指针和引用调用方法相关，从某种程度上来说，这是由继承控制的。公有继承建立is-a关系的一种方法是如何处理指向对象的指针和引用。通常，C++不允许将一种类型的地址赋给另一种类型的指针，也不允许一种类型的引用指向另一种类型。</p>
<p>然而，指向基类的引用或指针可以引用派生类对象，而不必进行显示转换。例如，下面的初始化是允许的：</p>
<pre><code>BrassPlus dilly(&quot;Annie&quot;, 226554, 3000);
Brass * pb = &amp;dilly;
Brass &amp; pb = dilly;
</code></pre><p><strong>将派生类引用或指针指向基类引用或指针被称为向上强制转换</strong>，这使公有继承不需要进行显示类型转换。该规则是is-a关系的一部分。BrassPlus对象都是Brass对象，因为它继承了Brass对象所有的数据成员和成员函数。所以，对Brass对象执行的任何操作，都适用于BrassPlus对象。因此，为处理Brass引用而设计的函数可以对BrassPlus对象执行同样的操作，而不必担心会导致任何问题。将指向对象的指针作为函数参数时，也是如此。<strong>向上强制转换是可以传递的。</strong>即，如果BrassPlus派生出BrassPlusPlus类，则Brass指针或引用可以引用Brass对象、BrassPlus对象和BrassPlusPlus对象。</p>
<p><strong>相反的过程—将基类指针或引用转换为派生类指针或引用—称为向下强制转换。</strong>如果不使用显式类型转换，则向下强制类型转换是不允许的。原因是is-a关系通常是不可逆的。派生类可以新增数据成员，因此使用这些数据成员的类成员函数不能用于基类。例如，假设从Employee类派生出Singer类，并添加了表示歌手音域的数据成员和用于报告音域的成员函数range()，则将range()方法用于Employee对象是没有意义的。但允许隐式向下强制转换，则可能无意间将指向Singer的指针设置为一个Employee对象的指针，并使用该指针来调用range()方法。参见图13.4</p>
<p><img src="https://i.imgur.com/EAn3eli.png" alt=""></p>
<p>对于使用基类引用或指针作为参数的函数调用，将进行向上转换。请看下面的代码，这里假设每个函数都调用虚方法ViewAcct():</p>
<pre><code>void fr(Brass &amp; rb);
void fp(Brass * pb);
void fv(Brass b);

int main()
{
    Brass b(&quot;Billy bee&quot;, 123456, 100.0);
    BrassPlus bp(&quot;Betty beep&quot;, 232313, 200.0);
    fr(b);   //使用Brass::ViewAcct()
    fr(bp);  //使用BrassPlus::ViewAcct()
    fp(b);   //使用Brass::ViewAcct()
    fp(bp);  //使用BrassPlus::ViewAcct()
    fv(b);   //使用Brass::ViewAcct()
    fv(bp);  //使用Brass::ViewAcct()
}
</code></pre><p>按值传递导致之间BrassPlus对象Brass部分传递给函数fv()。但按引用和指针发生的隐式向上转换导致函数fr()和fp()分别为Brass对象和BrassPlus对象使用Brass::ViewAcct()和BrassPlus::ViewAcct()。</p>
<p>隐式向上强制转换使基类指针或引用可以指向基类对象或派生类对象，因此需要动态联编。C++使用虚成员函数来满足这种需求。</p>
<h3 id="13-4-2-虚成员函数和动态联编"><a href="#13-4-2-虚成员函数和动态联编" class="headerlink" title="13.4.2 虚成员函数和动态联编"></a>13.4.2 虚成员函数和动态联编</h3><p>来回顾一个使用引用或指针调用方法的过程。请看下面的代码：</p>
<pre><code>BrassPlus ophelia;
Brass * bp;
bp = &amp;ophelia;
bp-&gt;ViewAcct();
</code></pre><p>正如前面指出的，如果基类中没有将ViewAcct()声明为虚函数，则bp-&gt;ViewAcct()将根据指针类型(Brass $\ast$)调用Brass::ViewAcct()。指针类型在编译时已知，因此编译器在编译时，可以将ViewAcct()关联到Brass::ViewAcct()。总之，编译器对非虚方法使用静态联编。</p>
<p>然而，如果在基类中将ViewAcct()声明为虚函数，则bp-&gt;ViewAcct()根据对象类型(BrassPlus)调用BrassPlus::ViewAcct()。在这个例子中，对象类型为BrassPlus，但通常(如程序13.10)只有在运行程序时才能确定对象的类型。所以编译器生成的代码将在程序执行时，根据对象类型将ViewAcct()关联到Brass::ViewAcct()或BrassPlus::ViewAcct()。总之，编译器对虚方法使用动态联编。</p>
<p>在大多数情况下，动态联编很好，因为它让程序能够选择为特定类型设计的方法。</p>
<p><strong>1. 为什么有两种类型的联编以及为什么默认为静态联编</strong></p>
<p>如果动态联编让您能够重新定义类方法，而静态联编在这方面很差，为何不摒弃静态联编呢？原因有两个—效率和概念模型。</p>
<p>首先来看效率。为使程序能够在运行阶段进行决策，必须采用一些方法来跟踪基类指针或引用指向的对象类型，这增加了额外的出实力开销。例如，如果类不会用作基类，则不需要动态联编。同样，如果派生类不重新定义基类的任何方法，也不需要动态联编。在这些情况下，使用静态联编更合理，效率也更高。<strong>由于静态联编的效率更高，因为被设置为默认选择</strong>。</p>
<p>接下来看概念模型。在设计类时，可能包含一些不在派生类重新定义的成员函数。例如，Brass::Balance()函数返回账户结余，不应该重新定义。不将该函数设置为虚函数，有两方面的好处：首先效率更高，其次，指出不要重新定义该函数。这表明，仅将那些预期将被重新定义的方法声明为虚方法。</p>
<p><strong>提示：</strong>如果要在派生类中重新定义基类的方法，则将它设置为虚方法；否则，设置为非虚方法。</p>
<p><strong>2. 虚函数工作原理</strong></p>
<p>C++规定了虚函数的行为，但将实现方法留给了编译器作者。不需要知道实现方法就可以使用虚函数，但了解虚函数的工作原理有助于更好地理解概念，因此，这里对其进行介绍。</p>
<p>通常，编译器处理虚函数的方法是：给每个对象添加一个隐藏成员。<strong>隐藏成员中保存了一个指向函数地址数组的指针。这种数组称为虚函数表（virtual function table, vtbl）。</strong>虚函数表中存储了为类对象进行声明的虚函数的地址。例如，基类对象包含一个指针，该指针指向基类中所有虚函数的地址表。派生类对象将包含一个指向独立地址表的指针。如果派生类提供了虚函数的新定义，该虚函数表将保存新函数的地址；如果派生类没有重新定义虚函数，该虚函数表将保存原始版本的地址。如果派生类定义了新的虚函数，则该函数的地址也将被添加到虚函数表(见图13.5)。注意，无论类中包含的虚函数是1个还是10个，都只需要在对象中添加1个地址成员，只是表的大小不同而已。</p>
<p><img src="https://i.imgur.com/1W3t0Wd.png" alt=""></p>
<p>调用虚函数时，程序将查看存储在对象中的vtbl地址，然后转向相应的函数地址表。如果使用类声明中定义的第一个虚函数，则程序将使用数组的第一个函数地址，并执行具有该地址的函数。依此类推。</p>
<p>总之，使用虚函数时，在内存和执行速度方面有一定的成本，包括：</p>
<ul>
<li>每个对象都将增大，增大量为存储地址的空间；</li>
<li>对于每个类，编译器都创建一个虚函数地址表(数组)；</li>
<li>对于每个函数调用，都需要执行一项额外的操作，即到表中查找地址。</li>
</ul>
<p>虽然非虚函数的效率比虚函数稍高，但不具备动态联编功能。</p>
<h3 id="13-4-3-有关虚函数注意事项"><a href="#13-4-3-有关虚函数注意事项" class="headerlink" title="13.4.3 有关虚函数注意事项"></a>13.4.3 有关虚函数注意事项</h3><p>虚函数的一些要点：</p>
<ul>
<li>在基类方法的声明中使用关键字virtual可使该方法在基类以及所有的派生类中是虚方法。</li>
<li>如果使用指向对象的引用或指针来调用虚方法，程序使用为对象类型定义的方法，而不使用为引用或指向类型定义的方法。这称为动态联编。基类指针或引用可以指向派生类对象。</li>
<li>如果定义的类被用作基类，则应将那些要在派生类中重新定义的类方法声明为虚方法。</li>
</ul>
<p>对于虚方法，还需了解的内容：</p>
<p><strong>1. 构造函数</strong></p>
<p>构造函数不能是虚函数。创建派生类对象时，将调用派生类的构造函数，而不是基类的构造函数，然后，派生类的构造函数将使用基类的一个构造函数，这种顺序不同于继承机制。因此，派生类不能继承类的构造函数，所以将类构造函数声明为虚函数没有意义。</p>
<p><strong>2. 析构函数</strong></p>
<p>析构函数应当是虚函数，除非类不用做基类。例如，Employee是基类，Singer是派生类，并添加了一个char $\ast$成员，该成员指向有new分配的内存。但Singer对象过期时，必须调用~Singer()析构函数来释放内存。</p>
<p>请看下面代码：</p>
<pre><code>Employee * pe = new Singer;
...
delete pe;
</code></pre><p>如果使用默认的静态联编，delete语句将调用~Employee()析构函数。这将释放由Singer对象中的Employee部分指向的内盘，但不会释放新的类成员指向的内存。但如果析构函数是虚函数，则上述代码将先调用~Singer()析构函数释放由Singer组件指向的内存，然后调用~Employee()析构函数来释放由Employee组件指向的内存。</p>
<p>这意味着，即使基类不需要显式析构函数提供服务，也不应依赖于默认构造函数，而应提供虚析构函数，即使它不执行任何操作：</p>
<pre><code>virtual ~BaseClass() {}
</code></pre><p><strong>给类定义一个虚析构函数并非错误，即使这个类不用做基类；这只是一个效率方面的问题。</strong></p>
<p><strong>提示：</strong>通常应给基类提供一个虚析构函数，即使它并不需要析构函数。</p>
<p><strong>3. 友元</strong></p>
<p><strong>友元函数不能是虚函数，因为友元函数不是成员函数，而只有成员函数才能是虚函数。</strong>如果由于这个原因引起设计问题，可以通常让友元函数使用虚成员函数来解决。</p>
<p><strong>4. 没有重新定义类</strong></p>
<p>如果派生类没有重新定义类，将使用该函数的基类版本。如果派生类位于派生链中，则将使用最新的虚函数版本，例外的情况是基类版本是隐藏的。</p>
<p><strong>5. 重新定义将隐藏方法</strong></p>
<p>假设创建了如下所示的代码：</p>
<pre><code>class Dwelling
{
public:
    virtual void showperks(int a) const;

};
class Hovel : public Dwelling
{
public:
    virtual void showperks() const;
};
</code></pre><p>这将导致问题，可能会出现类似于下面这样的编译器警告：</p>
<pre><code>Warning: Hovel::showperks(void) hides Dwelling::showperks(int)
</code></pre><p>也可能不会出现警告。但不管结果怎样，代码将具有如下含义：</p>
<pre><code>Hovel trump;
trump.showperks(); 
trump.showperks(5); //无效
</code></pre><p>新定义将showperks()定义为一个不接任何参数的函数。重新定义不会生成函数的两个重载版本，而是隐藏了接受一个int参数的基类版本。总之，重新定义继承的方法并不是重载。如果在派生类中重新定义函数，将不是使用相同的函数特征标覆盖基类的声明，而是隐藏同名的基类方法，不管参数特征标如何。</p>
<p>这引出了两条经验规则：</p>
<p>第一，如果重新定义继承的方法，应确保与原型完全相同，但如果返回类型是基类引用或指针，则可以修改为指向派生类的引用或指针。这种特性被称为<strong>返回类型协变</strong>，因为允许返回类型随类类型的变化而变化。</p>
<pre><code>class Dwelling
{
public:
    virtual Dwelling &amp; build(int n);
};
class Hovel : public Dwelling
{
public:
    virtual Hovel &amp; build(int n);
}
</code></pre><p>注意，这种例外只适用于返回值，而不适用于参数。</p>
<p>第二，如果基类声明被重载了，则应该派生类中重新定义所有的基类版本。</p>
<pre><code>class Dwelling
{
public:
    virtual void showperks(int n) const;
    virtual void showperks(double  x) const;
    virtual void showperks() const;
};
class Hovel : public Dwelling
{
public:
    virtual void showperks(int n) const;
    virtual void showperks(double  x) const;
    virtual void showperks() const;
}
</code></pre><p>如果只重新定义一个版本，则另外两个版本将被隐藏，派生类对象将无法使用它们。注意，如果不需要修改，则新定义课只调用基类版本：</p>
<pre><code>void Hovel::showperks() const { Dwelling::showperks(); }
</code></pre><h2 id="13-5-访问控制：-protected"><a href="#13-5-访问控制：-protected" class="headerlink" title="13.5 访问控制： protected"></a>13.5 访问控制： protected</h2><p>但目前位置，已经使用了关键字public和private来控制对类成员的访问。还存在另一个访问类别。这种类别使用关键字protected表示。关键字protected与private类似，在类外只能用公有类成员来访问protected部分中的类成员。<strong>private和protected之间的区别只有在基类派生的类中才会表现出来。派生类的成员可以直接访问基类的保护成员，单手能直接访问基类的私有成员。</strong>因此，对于外部来说，保护成员的行为和私有成员的行为相似；但对于派生类来说，保护成员的行为与公有成员类似。</p>
<p>例如，假如Brass类将balance成员声明为保护的：</p>
<pre><code>class Brass
{
protected:
    double balance;
};
</code></pre><p>在这种情况下，BrassPlus类可以直接访问balance，而不需要使用Brass方法。例如，可以这样编写BrassPlus::Withdraw()的核心：</p>
<pre><code>void BrassPlus::Withdraw(double amt)
{
    if (amt &lt; 0)
        cout &lt;&lt; &quot;Withdrawal amount must be positive; with drawal canceled.\n&quot;
    else if (amt &lt;= balance)
        balance -= amt
    else if ( amt &lt;= balance + maxLoan - owesBank)
    {
        double advance = amt - balance;
        owesBank += advance * (1.0 + rate);
        cout &lt;&lt; &quot;Bank advance: $&quot; &lt;&lt; advance &lt;&lt; endl;
        cout &lt;&lt; &quot;Finance charge: $&quot; &lt;&lt; advance * rate &lt;&lt; endl;
        Deposite(advance);
        balance -= amt;
    }
    else
        cout &lt;&lt; &quot;Credit limit exceed. Transaction cancelled.\n&quot;;
}
</code></pre><p>使用保护数据成员可以简化代码的编写工作，但存在设计缺陷。例如，继续以BrassPlus为例，如果balance是受保护的，则可以按下面的方式编写代码：</p>
<pre><code>void BrassPlus::Reset(double amt)
{
    balance = amt;
}
</code></pre><p>Brass类被设计成只能通过Deposit()和Withdraw()才能修改修改balance。但对于BrassPlusd对象，Reset()方法将忽略Withdraw()中的保护措施，实际上使balance称为公有变量。</p>
<p><strong>警告：</strong>最好对类数据成员采用私有访问控制，不要使用保护访问控制；同时通常基类方法使派生类能够访问基类数据。</p>
<p>然而，对于成员函数来说，保护访问控制很有用，它让派生类能够访问公众不能使用的内部函数。</p>
<h2 id="13-6-抽象基类"><a href="#13-6-抽象基类" class="headerlink" title="13.6 抽象基类"></a>13.6 抽象基类</h2><p>至此，介绍了简单继承和较为复杂的多态。接下来更为复杂的是抽象基类(abstract base class, ABC)。来看一些使用ABC的编程情况。</p>
<p>有时候，使用is-a规则并不是看上去的那样简单。例如，假设您正在开发一个图形程序，该程序会显式圆和椭圆。圆是椭圆的一个特殊情况—长轴等于短轴的椭圆。因此，所有的圆都是椭圆，可以从Ellipse类派生出Circle类。但设计细节时，发现很多问题。</p>
<p>首先，考虑Ellipse类包含的内容。数据成员可以包括椭圆中心的坐标、半长轴、短半轴以及方向角(水平坐标轴与长轴之间的角度)。另外，还可以包括一些移动椭圆、返回椭圆面积、旋转椭圆以及缩放长半轴和短半轴的方法：</p>
<pre><code>class Ellipse
{
private:
    double x;
    double y;
    double a;
    double b;
    double angle;
    ...
public:
    void Move(int nx, ny) {x = nx; y = ny;}
    virtual double Area cosnt { return 3.14159 * a *b; }
    virtual void Rotate(double nang) { angle += nang; }
    virtual void Scale(double sa, double sb) { a *= sa; b *= sb; }
    ...
};
</code></pre><p>现在假设从Ellipse派生出一个Circle类：</p>
<pre><code>class Circle : public Ellipse
{
...
};
</code></pre><p>虽然，圆是一种椭圆，但这种派生是笨拙的。例如，圆只需要一个值(半径)就可以描述大小和形状，并不需要长半轴和短半轴。Circle构造函数可以通过将同一个值赋给成员a和b来照顾这种情况，但将导致信息荣誉。angle参数和Rotate()方法对圆来说是没有实际意义的；而Scale()方法会将两个轴不同的缩放，将圆变成椭圆。可以使用一些技巧来修改这些问题，例如在Circle类中的私有成员部分包含重新定义的Rotate()方法，使Rotate()不能以公有方式用于圆。但总的来说，不是使用继承，直接定义Circle更简单：</p>
<pre><code>class Circle
{
private:
    double x;
    double y;
    double r;
    ...
public:
    void Move(int nx, ny) { x = nx; y = ny; }
    double Area() const { return 3.14159 * r *r; }
    void Scale(double sr) { r *= sr; }
    ...
};
</code></pre><p>现在，类只包含所需的成员。但这种解决方法的效率也不高。Circle和Ellipse类有很多共同点，将它们分别定义则忽略了这一事实。</p>
<p>还有一种解决办法是，即从Ellipse和Circle类中抽象出它们的共性，将这些特性放到一个ABC中。然后从该ABC派生出Circle和Ellipse类。这样，便可以使用基类指针数组同时管理Circle和Ellipse对象，即可以使用多态方法。</p>
<p>在这个例子中，这两个类的共同点是中心坐标、Move方法(对于两个类来说是相同的)和Area方法(对于两个类来说，是不同的)。确实，甚至不能再ABC中实现Area()方法，因此它没有包含必要的数据成员。<strong>C++通过使用纯虚函数提供未实现的函数。纯虚函数声明的结尾出为=0，</strong>参见Area()方法：</p>
<pre><code>class BaseEllipse
{
private:
    double x;
    double y;
    ...
public:
    BaseEllipse(double x0 = 0; double y0 =0) : x(x0), y(y0) {}
    virtual ~BaseEllipse() {}
    void Move(int nx, ny) { x = nx; y = ny; }
    virtual double Area() const = 0;  //纯虚函数
    ...
};
</code></pre><p><strong>当类声明包含纯虚函数时，则不能创建该类的对象。这里的理念是，包含纯虚函数的类只用作基类。__</strong>要成为真正的ABC，必须至少包含一个纯虚函数。__原型中的=0使虚函数成为纯虚函数。这里的方法Area()没有定义，但C++甚至允许纯虚函数没有定义。例如，也许所有的基类方法都与Move()一样，可以在基类中进行定义，但您仍需要将这个声明为抽象的。在这种情况下，可以将原型声明为虚的：</p>
<pre><code>void Move(int nx, ny) = 0;
</code></pre><p>这将使基类成为抽象的，但您仍可以实现文献中提供的方法：</p>
<pre><code>void BaseEllipse::Move(int nx, ny) {x = nx, y =ny; }
</code></pre><p><strong>总之，在原型中使用=0指出类是一个抽象基类，在类中可以不定义该函数。</strong></p>
<p>现在，可以从BaseEllipse类派生出Ellipse类和Circle类，添加所需的成员来完成每个类。需要注意的一点是，Circle类重视表示圆，而Ellipse类总是表示椭圆—也可以表示圆。然而，Ellipse类圆可以被重新缩放为非圆，而Circle类圆必须始终为圆。</p>
<p>使用这些类的程序将能够创建Ellipse对象和Circle对象，但是不能创建BaseEllipse对象。由于Circle和Ellipse对象的基类相同，因此可以使用BaseEllipse指针数组同时管理这两种对象。像Circle和Ellipse这样的类有时被称为具体类，这表示可以创建这些类型的对象。</p>
<p>总之，ABc描述的是至少使用一个纯虚函数的接口，从ABC派生出的类将根据派生类的具体特征，使用常规虚函数来实现这种接口。</p>
<h3 id="13-6-1-应用ABC概念"><a href="#13-6-1-应用ABC概念" class="headerlink" title="13.6.1 应用ABC概念"></a>13.6.1 应用ABC概念</h3><p>将ABC概念用于Brass和BrassPlus账户。首先定义一个名为AcctABC的ABC。这个类包含Brass和BrassPlus类共有的所有方法和数据成员，而那些在BrassPlus类和Brass类中的行为不同的方法应被声明为虚函数。至少应有一个虚函数是纯虚函数，这样才能使AcctABC称为抽象类。</p>
<p>程序13.11的头文件声明了AcctABC类、Brass类和BrassPlus类。为帮助派生类访问基类数据，AcctABc提供了一些保护方法：派生类方法可以调用这些方法，但它们并不是派生类对象的公有接口的组成部分。AcctABC还提供一个保护成员函数，用于处理格式化。另外，AcctABC类还有两个纯虚函数，所以它确实是抽象类。</p>
<p>程序13.11 acctabc.h</p>
<pre><code>#ifndef ACCTABC_H_
#define ACCTABC_H_
#include &lt;string&gt;
#include &lt;iostream&gt;
using std::string;

class AcctABC
{
private:
    string fullName;
    long acctNum;
    double balance;
protected:
    struct Formatting
    {
        std::ios_base::fmtflags flag;
        std::streamsize pr;
    };
    const string &amp; FullName() const { return fullName; }
    long AcctNum() const { return acctNum; }
    Formatting SetFormat() const;
    void Restore(Formatting &amp; f) const;
public:
    AcctABC(const string &amp; s = &quot;Nullbody&quot;, long an = -1,
        double bal = 0.0);
    void Deposit(double amt);
    virtual void Withdraw(double amt) = 0;
    double Balance() const { return balance; }
    virtual void ViewAcct() const = 0;
    virtual ~AcctABC() {}
};

class Brass : public AcctABC
{
public:
    Brass(const string &amp; s = &quot;Nullbody&quot;, long an = -1,
        double bal = 0.0) : AcctABC(s, an, bal) {}
    virtual void Withdraw(double amt);
    virtual void ViewAcct() const;
    virtual ~Brass() {}
};

class BrassPlus : public AcctABC
{
private:
    double maxLoan;
    double rate;
    double owesBank;
public:
    BrassPlus(const string &amp; s = &quot;Nullbody&quot;, long an = -1, 
          double bal = 0.0, double ml = 500,
          double r = 0.10);
    BrassPlus(const Brass &amp; ba, double ml = 500, double r = 0.10);
    virtual void Withdraw(double amt);
    virtual void ViewAcct() const;
    void ResetMax(double m) { maxLoan = m; }
    void ResetRate(double r) { rate = r; }
    void ResetOwes() { owesBank = 0; }
};

#endif
</code></pre><p>程序13.12 acctabc.cpp</p>
<pre><code>#include &quot;acctabc.h&quot;
#include &lt;iostream&gt;
using namespace std;

AcctABC::AcctABC(const string &amp; s, long an, double bal)
{
    fullName = s;
    acctNum = an;
    balance = bal;
}

void AcctABC::Deposit(double amt)
{
    if ( amt &lt; 0 )
        cout &lt;&lt; &quot;Negative deposit not allowed; deposite is cancelled.\n&quot;;
    else    
        balance += amt;
}

AcctABC::Formatting AcctABC::SetFormat() const
{
    Formatting f;
    f.flag = cout.setf(ios_base::fixed, ios_base::floatfield);
    f.pr = cout.precision(2);
    return f;
}

void AcctABC::Restore(Formatting &amp; f) const
{
    cout.setf(f.flag, ios_base::floatfield);
    cout.precision(f.pr);
}


void Brass::Withdraw(double amt)
{
    if (amt &lt; 0)
        cout &lt;&lt; &quot;Withdrawal amount must be positive; &quot;
             &lt;&lt; &quot;withdrawal cancelled.\n&quot;;
    else if (amt &lt;= Balance())
        Withdraw(amt);
    else
        cout &lt;&lt; &quot;Withdrawal amount of $&quot; &lt;&lt; amt
             &lt;&lt; &quot; exceeds your balance.\n&quot;
             &lt;&lt; &quot;Withdrawal cancelled.\n&quot;;
}

void Brass::ViewAcct() const 
{
    Formatting f = SetFormat();
    cout &lt;&lt; &quot;Barss client: &quot; &lt;&lt; FullName() &lt;&lt; endl;
    cout &lt;&lt; &quot;Account Number: &quot; &lt;&lt; AcctNum() &lt;&lt; endl;
    cout &lt;&lt; &quot;Balance: $&quot; &lt;&lt; Balance() &lt;&lt; endl;
    Restore(f);
}

BrassPlus::BrassPlus(const string &amp; s, long an, double bal,
        double ml, double r): AcctABC(s, an ,bal)
{
    maxLoan = ml;
    rate = r;
    owesBank = 0.0;
}

BrassPlus::BrassPlus(const Brass &amp; ba, double ml, double r)
        : AcctABC(ba)
{
    maxLoan = ml;
    rate = r;
    owesBank = 0.0;
}

void BrassPlus::ViewAcct() const
{
    Formatting f = SetFormat();

    cout &lt;&lt; &quot;BrassPlus Client: &quot; &lt;&lt; FullName() &lt;&lt; endl;
    cout &lt;&lt; &quot;Account Number: &quot; &lt;&lt; AcctNum() &lt;&lt; endl;
    cout &lt;&lt; &quot;Balance: $&quot; &lt;&lt; Balance() &lt;&lt; endl;
    cout &lt;&lt; &quot;Maximum loan: $&quot; &lt;&lt; maxLoan &lt;&lt; endl;
    cout &lt;&lt; &quot;Owed to bank: $&quot; &lt;&lt; owesBank &lt;&lt; endl;
    cout.precision(3);
    cout &lt;&lt; &quot;Loan Rate: &quot; &lt;&lt; 100 * rate &lt;&lt; &quot;%\n&quot;;
    Restore(f);
}

void BrassPlus::Withdraw(double amt)
{
    Formatting f = SetFormat();

    double bal = Balance();
    if (amt &lt;= bal)
        Withdraw(amt);
    else if (amt &lt;= bal + maxLoan - owesBank)
    {
        double advance = amt - bal;
        owesBank += advance * (1.0 + rate);
        cout &lt;&lt; &quot;Bank advance: $&quot; &lt;&lt; advance &lt;&lt; endl;
        cout &lt;&lt; &quot;Finance charge: $&quot; &lt;&lt; advance * rate &lt;&lt; endl;
        Deposit(advance);
        Withdraw(amt);
    }
    else
        cout &lt;&lt; &quot;Credit limit exceeded. Transaction cancelled.\n&quot;;
    Restore(f);
}
</code></pre><p>保护方法FullName()和AcctNum()提供了对数据成员fullName和acctNum的只读访问，使得可以进一步定制每个派生类的ViewAcct()。</p>
<p>这个版本在设置输出格式方面做了两项改进。前一个版本使用两个函数调用来设置输出格式，并使用一个函数调用来恢复格式：</p>
<pre><code>format initialState = setFormat();
precis prec = cout.precision(2);
...
restore(initialState, prec);
</code></pre><p>这个版本定义了一个结构，用于存储两项格式设置；并使用该结构来设置和恢复格式，因此只需要两个函数调用：</p>
<pre><code>struct Formatting
{
    std::ios_base::fmtflags flag;
    std::streamsize pr;
};

Formatting f = SetFormat();
...
Restore(f);
</code></pre><p>因此代码更整洁。</p>
<p>旧版本存在的问题是，setFormat()和restore()都是独立的函数，这些函数与客户定义的同名函数发生冲突。解决这种问题的方式有多种，一种方式是将这些函数声明为静态的，这样它们将归文件brass.cpp及其继任acctabc.cpp私有。另一种方式是，将这些函数以及结构Formatting放在一个独立的名称空间中。但这个示例探讨的主题之一是保护访问权限，因此将这些结构和函数放在了类定义的保护部分。这使得它们对基类和派生类可用，同时向外隐藏了它们。</p>
<p>对于Brass和BrassPlus账户的这种实现，使用方式与旧实现相同，因为类方法的名称和接口都与以前一样。例如，为程序13.10能够使用新的实现，需要采取下面的步骤将usebrass2.cpp转换为usebrass3.cpp</p>
<ul>
<li>使用acctabc.cpp而不是brass.cpp来连接usebrass.cpp</li>
<li>包含文件acctabc.h，而不是brass.h</li>
<li><p>将下面的代码替换</p>
<p>  Brass * p_clients[CLIENTS];</p>
</li>
</ul>
<p>替换为：</p>
<pre><code>AcctABC * p_clients[CLIENTS];
</code></pre><p>程序13.13usebrass3.cpp</p>
<pre><code>#include &lt;iostream&gt;
#include &quot;acctabc.h&quot;
#include &lt;string&gt;
const int CLIENTS = 4;
using namespace std;

int main()
{
    AcctABC * p_clients[CLIENTS];
    string temp;
    long tempnum;
    double tempbal;
    char kind;

    for (int i = 0; i &lt; CLIENTS; i++)
    {
        cout &lt;&lt; &quot;Enter client&apos;s name: &quot;;    
        getline(cin, temp);
        cout &lt;&lt; &quot;Enter client&apos;s account number: &quot;;
        cin &gt;&gt; tempnum;
        cout &lt;&lt; &quot;Enter opening balance: $&quot;;
        cin &gt;&gt; tempbal;
        cout &lt;&lt; &quot;Enter 1 for Brass Account or &quot;
             &lt;&lt; &quot;2 for BrassPlus Account: &quot;;
        while (cin &gt;&gt; kind &amp;&amp; (kind != &apos;1&apos; &amp;&amp; kind != &apos;2&apos;))
            cout &lt;&lt; &quot;Enter either 1 or 2: &quot;;
        if (kind == &apos;1&apos;)
            p_clients[i] = new Brass(temp, tempnum, tempbal);
        else
        {
            double tmax, trate;
            cout &lt;&lt; &quot;Enter the overdraft limit: $&quot;;
            cin &gt;&gt; tmax;
            cout &lt;&lt; &quot;Enter the interest rate &quot;
                 &lt;&lt; &quot;as a decimal fraction: &quot;;
            cin&gt;&gt; trate;
            p_clients[i] = new BrassPlus(temp, tempnum, tempbal, 
                            tmax, trate);
        }
        while (cin.get() != &apos;\n&apos;)
            continue;
    }
    cout &lt;&lt; endl;
    for (int i = 0; i &lt; CLIENTS; i++)
    {
        p_clients[i]-&gt;ViewAcct();
        cout &lt;&lt; endl;
    }

    for (int i = 0; i &lt; CLIENTS; i++)
    {
        delete p_clients[i];
    }
    cout &lt;&lt; &quot;Done!\n&quot;;
    return 0;
}
</code></pre><p>该程序的行为与非抽象基类版本相同，因此如果输入与给程序13.10提供的输入相同，输出也将相同。</p>
<h3 id="13-6-2-ABC理念"><a href="#13-6-2-ABC理念" class="headerlink" title="13.6.2 ABC理念"></a>13.6.2 ABC理念</h3><p>在处理继承的问题上，RatePlayer示例使用的方法比较随意，而ABC方法比它更具系统性、更规范。设计ABC之前，首先应开发一个模型—指出编程问题所需的类以及它们之间的相互关系。一种学院派思想认为，如果要设计类继承层次，则只能将那些不会被用作基类的类设计为具体的类。这种方法的设计更清晰，复杂程度更低。</p>
<p>可以将ABC看作是一种必须实施的接口。ABC要求具体派生类覆盖其纯虚函数—迫使派生类遵循ABC设置的接口规则。这种模型在基于组件的编程模式中很常见，在这种情况下，使用ABC使得组件设计人员能够制定“接口约定”，这样确保了从ABC派生的所有组件都至少支持ABC制定的功能。</p>
<h2 id="13-7-继承和动态分配"><a href="#13-7-继承和动态分配" class="headerlink" title="13.7 继承和动态分配"></a>13.7 继承和动态分配</h2><p>继承是怎样与动态内存分配(使用new和delete)进行互动的呢？例如，如果基类使用动态内存分配，并重新定义赋值运算符或复制构造函数，这将怎么影响派生类的实现呢？这个问题的答案取决于派生类的属性。如果派生类也使用动态内存分配，那么就需要学习几个新的小技巧。下面来看看这两种情况。</p>
<h3 id="13-7-1-第一种情况：派生类不使用new"><a href="#13-7-1-第一种情况：派生类不使用new" class="headerlink" title="13.7.1 第一种情况：派生类不使用new"></a>13.7.1 第一种情况：派生类不使用new</h3><p>假设基类使用了动态内存分配：</p>
<pre><code>class baseDMA
{
private:
    char * label;
    int rating;
public:
    baseDMA(const char * l = &quot;null&quot;, int r = 0);
    baseDMA(const baseDMA &amp;);j
    virtual ~baseDMA();
    baseDMA &amp; operator=(cosnt baseDMA &amp; rs);
...
};
</code></pre><p>声明中包含了构造函数使用new时需要的特殊方法：析构函数、复制构造函数和重载赋值运算符。</p>
<p>现在，从baseDMA派生出lackDMA类，而后者不使用new，也未包含其他一些不常用的、需要特殊处理的设计特性：</p>
<pre><code>class lackDMA : pulic baseDMA
{
private:
    char color[40];
public:
...
}
</code></pre><p>是否需要为lackDMA类定义显式析构函数、复制构造函数和赋值运算符呢？不需要。</p>
<p>首先，来看是否需要析构函数。如果没有定义析构函数，编译器将定义一个不执行任何操作的默认构造函数。实际上，派生类的默认构造函数总是要进行一些操作的：执行自身的代码后调用基类析构函数。因为我们假设lackDMA成员不执行任何特殊操作，所以默认析构函数是合适的。</p>
<p>接着来看赋值构造函数。第12章介绍过，默认复制构造函数执行成员复制，这对于动态内存分配来说是不合适的。但对于新的lackDMA成员来说是合适的。因此只需考虑继承的basedDMA对象。要知道，成员复制将根据数据类型采用相应的复制方式，因此，将long复制到long中通过使用常规复制完成的；但复制类成员或继承的类组件时，则是使用该类的复制构造函数完成的。所以，lackDMA类的默认复制构造函数使用显式baseDMA复制构造函数来复制lackDMA对象的baseDMA部分。因此，默认复制构造函数对于新的lackDMA成员来说是合适的，同时对于继承的baseDMA对象来说也是合适的。</p>
<p>对于赋值来说，也是如此。类的默认赋值运算符将自动使用基类的赋值运算符来对基类组件进行赋值。因此，默认赋值运算符也是合适的。</p>
<p>派生类对象的这些属性也适用于本身对象的类成员。例如，第10章介绍过，实现Stock类时，可以使用string对象而不是char数组来存储公司名称。标准string类和本书前面创建的String类一样，也采用动态内存分配。现在，读者知道了为何这不会引发问题。Stock的默认复制构造函数将使用string的复制构造函数来复制对象company成员；Stock的默认赋值运算符将使用strig的赋值运算符来给对象的company成员赋值；而Stock的析构函数将自动调用string的析构函数。</p>
<h3 id="12-7-2-第二种情况：派生类使用new"><a href="#12-7-2-第二种情况：派生类使用new" class="headerlink" title="12.7.2 第二种情况：派生类使用new"></a>12.7.2 第二种情况：派生类使用new</h3><p>假设派生类使用了new:</p>
<pre><code>class hasDMA : public baseDMA
{
private:
    char * style;
public:
...
};
</code></pre><p>在这种情况下，必须为派生类定义显式析构函数、赋值用品认识和复制构造函数。下面依次考虑这些方法。</p>
<p>派生类析构函数自动调用基类的析构函数，故其自身的职责是对派生类构造函数执行的工作进行清理。因此hasDMA析构函数必须释放指针style管理的内存，并依赖于basdDMA的析构函数来释放指针label管理的内存。</p>
<pre><code>baseDMA::~baseDMA()
{
    delete [] label;
}

hasDMA::~hasDMA()
{
    delete [] style;
}
</code></pre><p>接下来看复制构造函数。BaseDMA的复制构造函数遵循用char数组的常规模式，即使用strlen()来获悉存储C-风格字符串所需的空间、分配足够的内存（字符数加上存储空字符所需的1字节）并使用函数strcpy()将原始字符串赋值到目的地：</p>
<pre><code>baseDMA::baseDMA(const baseDMA &amp; rs)
{
    label = new char[std::strlen(rs.label) + 1];
    std::strcpy(label, rs.label);
    rating = rs.rating;
}
</code></pre><p>hasDMA复制构造函数只能访问hasDMA的数据，因此它必须调用baseDMA复制构造函数来处理共享的baseDMA数据：</p>
<pre><code>hasDMA::hasDMA(const hasDMA &amp; hs) : baseDMA(hs)
{
    style = new char[std::strlen(hs.style + 1)];
    stdJ::strcpy(style, hs.style);
}
</code></pre><p>需要注意的一点是，成员初始化列表将一个hasDMA引用传递给baseDMA构造函数。没有参数类型为hasDMA引用的base构造函数，也不需要这样的构造函数。因为复制构造函数baseDMA有一个baseDMA引用参数，而基类可以指向派生类型。因此baseDMA复制构造函数将使用hasDMA参数的baseDMA部分来构造新对象的baseDMA部分。</p>
<p>接下来看赋值运算符。BaseDMA赋值运算符遵循下述常规模式：</p>
<pre><code>baseDMA &amp; baseDMA::operator(const baseDMA &amp; rs)
{
    if (this == &amp;rs)
        return *this;
    delete [] label;
    label = new char[std::strlen(rs.label) + r];
    std::strcpy(label, rs.label);
    rating = rs.rating;
    return *this;
}
</code></pre><p>由于hasDMA也使用动态内存分配，所以它需要一个显式赋值运算符。作为hasDMA的方法，它只能直接访问hasDMA的数据。然而，派生类的显式赋值运算符必须负责所有的baseDMA基类对象的赋值，可以通过显式调用基类赋值运算符来完成这项工作，如下所示：</p>
<pre><code>hasDMA &amp; hasDMA::operator=(cosnt hasDMA &amp; hs)
{ 
    if ( this == &amp;hs)
        return *this;
    baseDMA::operator=(hs);
    delete [] style;
    style = new char [std::strlen(hs.style) + 1];
    std::strcpy(style, hs.style);
    return *this;
}
</code></pre><p>下述语句看起来有点奇怪：</p>
<pre><code>baseDMA::operator=(hs);
</code></pre><p>但是通过使用函数表示法，而不是运算符表示法，可以使用作用域解析运算符。实际上，该语句的含义如下：</p>
<pre><code>*this = hs;  //使用baseDMA::operator=();
</code></pre><p>当然，编译器将忽略注释，所以使用后面的代码时，编译器将使用hasDMA::operator=()，从而形程递归调用。使用函数表示使得赋值运算符被正确调用。</p>
<p>总之，当基类和派生类都采用动态内存分配时，派生类的析构函数、复制构造函数和赋值运算符都必须使用相应的基类方法来处理基类元素。这种要求通过三种不同的方式来满足。对于析构函数，这是自动完成的；对于构造函数，这是通过在初始化成员列表中调用基类的复制构造函数来完成的；如果不这样做，将自动调用基类的默认构造函数。对于赋值运算符，这是通过使用作用域解析运算符乡试第调用基类的赋值运算符来完成的。</p>
<h3 id="13-7-3-使用动态内存分配和友元的继承示例"><a href="#13-7-3-使用动态内存分配和友元的继承示例" class="headerlink" title="13.7.3 使用动态内存分配和友元的继承示例"></a>13.7.3 使用动态内存分配和友元的继承示例</h3><p>为演示这些有关继承和动态分配的概念，我们将刚才介绍过的baseDMA、lackDMA和hasDMA类集成到一个示例中。程序13.14是这些类的头文件。除前面介绍的内容外，这个头文件还包含一个友元函数，以说明派生类如何访问基类的友元。</p>
<p>程序13.14 dma.h</p>
<pre><code>#ifndef DMA_H_
#define DMA_H_
#include &lt;iostream&gt;

class baseDMA
{
private:
    char * label;
    int rating;
public:
    baseDMA(const char * l = &quot;null&quot;, int r = 0);
    baseDMA(const baseDMA &amp; rs);
    virtual ~baseDMA();
    baseDMA &amp; operator=(const baseDMA &amp; rs);
    friend std::ostream &amp; operator&lt;&lt;(std::ostream &amp; os, const baseDMA &amp; rs);
};


class lackDMA : public baseDMA
{
private:
    enum {COL_LEN = 40};
    char color[COL_LEN];
public:
    lackDMA(const char * c = &quot;blank&quot;, const char * l = &quot;null&quot;,
             int r = 0);
    lackDMA(const char * c, const baseDMA &amp; rs);
    friend std::ostream &amp; operator&lt;&lt;(std::ostream &amp; os, const lackDMA &amp; rs);
};

class hasDMA : public baseDMA
{
private:
    char * style;
public:
    hasDMA(const char * c = &quot;none&quot;, const char * l = &quot;null&quot;,
            int r = 0);
    hasDMA(const char * c, const baseDMA &amp; rs);
    hasDMA(const hasDMA &amp; hs);
    hasDMA &amp; operator=(const hasDMA &amp; hs);
    ~hasDMA();
    friend std::ostream &amp; operator&lt;&lt;(std::ostream &amp; os, const hasDMA &amp; rs);
};

#endif
</code></pre><p>程序13.15 dma.cpp</p>
<pre><code>#include &lt;iostream&gt;
#include &quot;dma.h&quot;
#include &lt;cstring&gt;
using namespace std;

baseDMA::baseDMA(const char * l, int r)
{
    label = new char[strlen(l) + 1];
    strcpy(label, l);
    rating = r;
}

baseDMA::baseDMA(const baseDMA &amp; rs)
{
    label = new char[strlen(rs.label) + 1];
    strcpy(label, rs.label);
    rating = rs.rating;
}

baseDMA::~baseDMA()
{
    delete [] label;
}

baseDMA &amp; baseDMA::operator=(const baseDMA &amp; rs)
{
    if (this == &amp;rs)
        return *this;
    delete [] label;
    label = new char[strlen(rs.label) + 1];
    strcpy(label, rs.label);
    rating = rs.rating;
    return *this;
}

ostream &amp; operator&lt;&lt;(ostream &amp; os, const baseDMA &amp; rs)
{
    os &lt;&lt; &quot;Label: &quot; &lt;&lt; rs.label &lt;&lt; endl;
    os &lt;&lt; &quot;Rating: &quot; &lt;&lt; rs.rating &lt;&lt; endl; 
    return os;
}

lackDMA::lackDMA(const char * c, const char * l, int r) : baseDMA(l, r)
{
    strncpy(color, c, COL_LEN-1);
    color[COL_LEN-1] = &apos;\0&apos;;
}

lackDMA::lackDMA(const char * c, const baseDMA &amp; rs) : baseDMA(rs)
{
    strncpy(color, c, COL_LEN-1);
    color[COL_LEN-1] = &apos;\0&apos;;
}

ostream &amp; operator&lt;&lt;(ostream &amp; os, const lackDMA &amp; rs)
{
    os &lt;&lt; (const baseDMA &amp;) rs;
    os &lt;&lt; &quot;Color: &quot; &lt;&lt; rs.color &lt;&lt; endl;
    return os;
}

hasDMA::hasDMA(const char * s, const char * l, int r) : baseDMA(l, r)
{
    style = new char[strlen(s) + 1];
    strcpy(style, s);
}

hasDMA::hasDMA(const char * s, const baseDMA &amp; hs) : baseDMA(hs)
{
    style = new char[strlen(s) + 1];
    strcpy(style, s);
}

hasDMA::hasDMA(const hasDMA &amp; hs) : baseDMA(hs)
{
    style = new char[strlen(hs.style) + 1];
    strcpy(style, hs.style);
}

hasDMA::~hasDMA()
{
    delete [] style;
}
hasDMA &amp; hasDMA::operator=(const hasDMA &amp; hs)
{
    if (this == &amp;hs)
        return *this;
    baseDMA::operator=(hs);
    delete [] style;
    style = new char[strlen(hs.style) + 1];
    strcpy(style, hs.style);
    return *this;
}

ostream &amp; operator&lt;&lt;(ostream &amp; os, const hasDMA &amp; rs)
{
    os &lt;&lt; (const baseDMA &amp;) rs;
    os &lt;&lt; &quot;Style: &quot; &lt;&lt; rs.style &lt;&lt; endl;
    return os;
}
</code></pre><p>在程序13.14和13.15中，需要注意的特性是，派生类如何使用基类的友元。例如，请考虑下面这个hasDMA类的友元：</p>
<pre><code>friend std::ostream &amp; operator&lt;&lt;(std::ostream &amp; os, const hasDMA &amp; rs);
</code></pre><p>作为hasDMA类的友元，该函数能够访问style成员。然而，还存在一个问题：该函数如不是baseDMA类的友元，那它如何访问成员label和rating呢？答案是使用baseDMA类的友元函数operator&lt;&lt;()。</p>
<p>下一个问题是，因为友元不是成员函数，所以不能使用作用域解析运算符指出使用哪个函数。这个问题的解决办法是使用强制类型转换，以便匹配原型能够选择正确的函数。因此代码将参数const hasDMA &amp; 转换成类型const baseDMA &amp;的参数：</p>
<pre><code>ostream &amp; operator&lt;&lt;(ostream &amp; os, const hasDMA &amp; rs)
{
    os &lt;&lt; (const baseDMA &amp;) rs;
    os &lt;&lt; &quot;Style: &quot; &lt;&lt; rs.style &lt;&lt; endl;
    return os;
}
</code></pre><p>程序13.16是一个测试小程序。</p>
<p>程序13.16 usedma.cpp</p>
<pre><code>#include &quot;dma.h&quot;
#include &lt;iostream&gt;
#include &lt;string&gt;
using namespace std;

int main()
{
    baseDMA shirt(&quot;Portabelly&quot;, 8);
    lackDMA balloon(&quot;red&quot;, &quot;Blimpo&quot;, 4);
    hasDMA map(&quot;Mercator&quot;, &quot;Buffalo Keys&quot;, 5);
    cout &lt;&lt; &quot;Display baseDMA object:\n&quot;;
    cout &lt;&lt; shirt &lt;&lt; endl;
    cout &lt;&lt; &quot;Display lackDMA object:\n&quot;;
    cout &lt;&lt; balloon &lt;&lt; endl;
    cout &lt;&lt; &quot;Display hasDMA object:\n&quot;;
    cout &lt;&lt; map &lt;&lt; endl;
    lackDMA balloon2(balloon);
    cout &lt;&lt; &quot;Result of lackDMA copy:\n&quot;;
    cout &lt;&lt; balloon2 &lt;&lt; endl;
    hasDMA map2;
    map2 = map;
    cout &lt;&lt; &quot;Resutl of hasDMA assignment:\n&quot;;
    cout &lt;&lt; map2 &lt;&lt; endl;
    return 0;
}
</code></pre><p>输出：</p>
<pre><code>Display baseDMA object:
Label: Portabelly
Rating: 8

Display lackDMA object:
Label: Blimpo
Rating: 4
Color: red

Display hasDMA object:
Label: Buffalo Keys
Rating: 5
Style: Mercator

Result of lackDMA copy:
Label: Blimpo
Rating: 4
Color: red

Resutl of hasDMA assignment:
Label: Buffalo Keys
Rating: 5
Style: Mercator
</code></pre><h2 id="13-8-类设计回顾"><a href="#13-8-类设计回顾" class="headerlink" title="13.8 类设计回顾"></a>13.8 类设计回顾</h2><p>C++可用于解决各种类型的编程问题，但不能将类设计简化成带编号的例程。然而，有些常用的知道原则，下面复习并拓展前面的讨论。</p>
<h3 id="13-8-1-编译器生成的成员函数"><a href="#13-8-1-编译器生成的成员函数" class="headerlink" title="13.8.1 编译器生成的成员函数"></a>13.8.1 编译器生成的成员函数</h3><p>第12章介绍过，编译器会自动生成一些公有的成员函数—特殊成员函数。这表明这些特殊成员函数很重要，下面回顾中其的一些。</p>
<p><strong>1. 默认构造函数</strong></p>
<p>默认构造函数要么没有参数，要么所有的参数都有默认值。如果没有定义任何构造函数，编译器将制定默认构造函数，让您能够创建对象。例如，假设Star类是一个类，则下述代码需要使用默认构造函数：</p>
<pre><code>Star rigel;
Star pleiades[6];  //创建一个对象数组
</code></pre><p>自动生成的默认构造函数的另一项功能是，调用基类的默认构造函数以及调用本身是对象的成员所属的类的默认构造函数。</p>
<p>另外，如果派生类构造函数的成员初始化列表中没有显式调用基类构造函数，则编译器将使用基类的默认构造函数来构造派生类对象的基类部分。在这种情况下，如果基类没有构造函数，将导致编译阶段错误。</p>
<p>如果定义了某种构造函数，编译器将不会定义默认构造函数。在这种情况下，如果需要默认构造函数，则必须自己提供。</p>
<p>提供构造函数的动机之一是确保对象总能被正确地初始化。另外，如果类包含指针成员，则必须初始化这些成员。因此，最好提供一个显式默认构造函数，将所有的类数据成员都初始化为合理的值。</p>
<p><strong>2. 复制构造函数</strong></p>
<p>复制构造函数接受其所属的对象作为参数。例如，Star类的复制构造函数的原型如下：</p>
<pre><code>Star(const star &amp;);
</code></pre><p>在下述情况下，将使用复制构造函数：</p>
<ul>
<li>将新对象初始化为一个同类对象；</li>
<li>按值将对象传递给函数；</li>
<li>函数按值返回对象；</li>
<li>编译器生成临时对象。</li>
</ul>
<p>如果程序没有使用(隐式或显式)复制构造函数，编译器将提供原型，但不提供函数定义；否则，程序将定义一个执行成员初始化的复制构造函数。即，新对象的每个成员都被初始化为原始对象相应成员的值。如果成员为类对象，则初始化该成员时，将使用相应类的复制构造函数。</p>
<p>在某些情况下，成员初始化是不合适的。例如，使用new初始化的成员指针通常要求执行深度复制，或者类可能包含需要修改的静态变量。在上述情况下，需要定义自己的复制构造函数。</p>
<p><strong>3. 赋值运算符</strong></p>
<p>默认的赋值运算符用于处理同类对象之间的赋值。不要将赋值和初始化混淆了。如果语句创建新的对象，则使用初始化；如果语句修改已有的对象的值，则是赋值：</p>
<pre><code>Star sirius;    
Star alpha = sirius; //初始化
Star dogstar;
dogstar = sirius; //赋值
</code></pre><p>默认赋值为成员赋值。如果成员为类对象，则默认成员赋值将使用相应类的赋值运算符。如果需要显式定义复制构造函数，则基于相同的原因，也需要显式定义赋值运算符。Star类的赋值运算符的原型如下：</p>
<pre><code>Star &amp; operator=(const Star &amp;);
</code></pre><p>赋值运算符函数返回一个Star对象的引用。baseDMA类演示了一个典型的显式赋值运算函数示例。</p>
<p>编译器将不会生成一种类型赋给另一种类型的赋值运算符。如果希望能够将字符串赋给Star对象，则方法之一是显式定义下面的运算符:</p>
<pre><code>Star &amp; Star::operator=(const char * c) {...}
</code></pre><p>另一种方法是使用转换函数将字符串转换成Star对象，然后使用将Star赋给Star的赋值函数。第一种方法的运行速度快，但代码较复杂，而是用转换函数可能导致编译器出现混乱。</p>
<p>第18章将讨论C++新增的两种特殊方法： 移动构造函数和移动赋值运算符。</p>
<h3 id="13-8-2-其他的在方法"><a href="#13-8-2-其他的在方法" class="headerlink" title="13.8.2 其他的在方法"></a>13.8.2 其他的在方法</h3><p>定义类时，还需要注意几点。</p>
<p><strong>1. 构造函数</strong></p>
<p>构造函数不同其他类方法，因为它创建新的对象，而其他方法只是被现有的对象调用。这是构造函数不能被继承的原因之一。继承意味着派生对象可以使用基类的方法，然而，构造函数在完成其工作之前，对象并不存在。</p>
<p><strong>2. 析构函数</strong></p>
<p>一定要定义显式析构函数来释放类构造函数使用new分配的所有内存，并完成类对象所需的任何特殊的清理工作。对于，基类，即使它不需要析构函数，也应提供一个虚析构函数。</p>
<p><strong>3. 转换</strong></p>
<p>使用一个参数就可以调用的构造函数定义了参数类型到类类型的转换。例如，下述Star类的构造函数原型：</p>
<pre><code>Star(const char *);                         //转换char为Star                       
  Star(const Spectral &amp;, int members = 1);   //转换Spectral为Star
</code></pre><p>将可转换的类型传递给类为参数的函数时，将调用转换构造函数。例如，在如下的代码中：</p>
<pre><code>Star north;
north = &quot;polaris&quot;;
</code></pre><p>第二条语句将调用Star::operator=(const star $\ast$)函数，使用Star::star(const char $\ast$)生成一个Star对象，该对象将被用作上述赋值运算符函数的参数。这里假设没有定义char$\ast$赋给Star的赋值运算符。</p>
<p>在带一个参数的构造函数原型中使用explicict将禁止进行隐式转换，但仍允许显式转换：</p>
<pre><code>class Star
{
...
public:
    explicit Star(const char *);
...
};

Star north;
north = &quot;polaris&quot;;        //禁止
north = Star(&quot;polaris&quot;);
</code></pre><p>要将类对象转换为其他类型，应定义转换函数(参见第11章)。转换函数可以是没有参数的类成员函数，也可以是返回类型被声明为目标类型的类成员函数。即使没有声明返回类型，函数也应该返回所需的转换值。下面是一些示例：</p>
<pre><code>Star::operator double() {}   //转换Star为double
Star::operator const char * {}  //转换Star为const char
</code></pre><p>应理智地使用这样函数，仅当它们有帮助时才使用。另外，对于某些类，包含转换函数将增加代码的二义性。例如，假设已经为第11章的Vector类型定义了double转换函数，并编写了下面的代码：</p>
<pre><code>Vector ius(6.0, 0.0);
Vector lus = ius + 20.2; //二义性
</code></pre><p>编译器可以将ius转换成double并使用double加法，或将20.2转换成Vector（使用构造函数之一）并使用Vector加法。但除了二义性外，它什么也不做。</p>
<p>C++11支持关键字explicit用于转换函数。于构造函数一样，explicit允许使用强制类型转换进行显示转换，但不允许隐式转换。</p>
<p><strong>4. 按值传递对象与传递引用</strong></p>
<p>通常，编写使用算真相作为参数的函数时，应按引用而不是按值来传递对象。这样做的原因之一是为了提供效率。按值传递对象涉及到生成临时对象拷贝，即调用复制构造函数，然后调用析构函数。调用这些函数需要时间，复制大型对象比传递引用花费的时间要多得多。如果函数不修改对象，应将参数声明为const引用。</p>
<p>按引用传递对象的另一个原因，在继承使用虚函数时，被定义为接受基类引用参数的函数可以接受派生类。这在本章前面介绍过。</p>
<p><strong>5. 返回对象和返回引用</strong></p>
<p>有些类方法返回对象。可能注意到了，有些成员函数直接返回对象，而另一些则返回引用。有时必须返回对象，但如果可以不返回对象，则应返回引用。</p>
<p>首先，在编码方面，直接返回对象与返回引用之间唯一的区别在于函数原型和函数头：</p>
<pre><code>Star noval(const char &amp;);
Star &amp; noval(const char &amp;);
</code></pre><p>其次，应返回引用而不是返回对象的原因在于，返回对象涉及生成返回对象的临时副本，这是调用函数的程序可以使用的副本。因此，返回对象的时间成本包括调用复制构造函数生成副本所需的时间和调用析构函数删除副本所需的时间。返回引用可节省时间和内存。直接返回对象与按值传递对象相似：它们都生成临时副本。同样，返回引用和按引用传递对象相似：调用和被调用的函数对同一个对象进行操作。</p>
<p>然而，并不总是可以返回引用。函数不能返回在函数中创建的临时对象的引用，因为当函数结束时，临时对象将消失，因此这种引用将是非法的。在这种情况下，应返回对象，以生成一个调用程序可以使用的副本。</p>
<p>通常的规则是，如果函数返回在函数中创建的临时对象，则不要使用引用。例如，下面的方法使用构造函数来创建一个新对象，然后返回对象的副本：</p>
<pre><code>Vector Vector::operator+(const Vector &amp; b) const
{
    return Vector(x+b.x, y+b,y);
}
</code></pre><p>如果函数返回的是通过引用或指针传递给它的对象，则应按引用返回对象。例如，下面的代码按引用返回调用函数的对象或作为参数传递给函数对象：</p>
<pre><code>const Stock &amp; Stock::topval(const Stock &amp; s) const
{
    if (s.total_val &gt; total_val)
        return s;
    else
        return *this;
}
</code></pre><p><strong>6. 使用const</strong></p>
<p>使用const时应特别注意。可以用它来确保方法不修改参数：</p>
<pre><code>Star::Star(const char * s) {}
</code></pre><p>可以使用const来确保方法不修改调用它的对象：</p>
<pre><code>void Star::Show() const {}
</code></pre><p>这里const表示const Star $\ast$this，而this指向调用的对象。</p>
<p>通常，可以将返回引用的函数放在赋值语句的左侧，实际上意味着可以将赋值给引用的对象。但可以使用const来确保引用或指针返回的值不能用于修改对象的数据:</p>
<pre><code>const Stock &amp; Stock::topval(const Stock &amp; s) const
{
    if (s.total_val &gt; total_val)
        return s;
    else
        return *this;
}
</code></pre><p>该方法返回对this或s的引用。因为thi和s都被声明为const，所以函数不能对它们进行修改，这意味着返回的引用也必须被声明为const。</p>
<p>注意，如果函数将参数声明为指向const的引用或指针，则不能将该参数传递给另一个函数，除非后者也确保了参数不会被修改。</p>
<h3 id="13-8-3-公有继承的考虑因素"><a href="#13-8-3-公有继承的考虑因素" class="headerlink" title="13.8.3 公有继承的考虑因素"></a>13.8.3 公有继承的考虑因素</h3><p>通常，在程序中使用继承时，有很多问题需要注意。</p>
<p><strong>1. is-a关系</strong></p>
<p>要遵循is-a关系。如果派生类不是一种特殊的基类，则不用使用公有派生。例如，不应从Brain类派生出Programer类。如果要指出程序员有大脑，应将Brain类对象作为Programmer类的成员。</p>
<p>在某些情况下，最好的方法可能是创建包含纯虚函数的抽象数据类，并从它派生出其他的类。</p>
<p>请记住，表示is-a关系的方式之一是，无需进行显式类型转换，基类指针就可以指向派生类对象，基类引用可以引用派生类对象。另外，返回来是行不通的。这种显式类型转换(向下强制转换)可能有意义，也可能没有，这取决于类声明(参见图13.4)。</p>
<p><strong>2. 什么不能被继承</strong></p>
<p>构造函数是不能继承的，即，创建派生类对象时，必须调用派生类对象构造函数。然而，派生类对象构造函数通常使用成员初始化列表语法来调用基类构造函数，以创建派生类对象的基类部分。如果派生类对象没有使用成员初始化列表语法显式调用基类构造函数，将使用基类的默认构造函数。在继承链中，每个类都可以使用成员初始化列表将信息传递给相邻的基类。</p>
<p>析构函数也是不能继承的。然而，在释放对象时，程序首先调用派生类的析构函数，然后调用基类的析构函数。如果基类有默认析构函数，编译器将为派生类生成默认析构函数。通常，对于基类，其析构函数应设置为虚的。</p>
<p>赋值运算符是不能继承的，原因很简单。派生类继承的方法的特征标与基类完全相同，但赋值运算符的特征标随类而异，这是因为它包含一个类型为其所属类的形参。</p>
<p><strong>3. 赋值运算符</strong></p>
<p>如果编译器发现程序将一个对象赋给同一个类的另一个对象，它将自动为这个类提供一个赋值运算符。这个运算符的默认或隐式版本将采用成员赋值，即将原对象的相应成员赋给目标对象的每个成员。然而，如果对象属于派生类，编译器将使用基类赋值运算符来处理派生类对象中基类部分的赋值。如果显式地为基类提供了赋值运算符，将使用该运算符。与此相似，如果成员是另一个类的对象，则对于该成员，将使用其所属的赋值运算符。</p>
<p>如果类构造函数使用new来初始化指针，则需要提供一个显式赋值运算符。因为它对于派生对象的基类部分，C++将使用基类的赋值运算符，所以不需要派生类重新定义赋值运算符，除非它添加了需要特别注意的数据成员。例如，baseDMA类显式地定义了赋值，但派生类lackDMA使用它生成的隐式赋值运算符。</p>
<p>然而，如果派生类使用了new，则必须提供显式赋值运算符。必须给类的每个成员提供赋值运算符，而不仅仅是新成员。hasDMA类演示了如何完成这项工作：</p>
<pre><code>hasDMA &amp; hasDMA::operator=(const hasDMA &amp; hs)
{
    if (this == &amp;hs)
        return *this;
    baseDMA::operator=(hs);
    delete [] style;
    style = new char[strlen(hs.style) + 1];
    strcpy(style, hs.style);
    return *this;
}
</code></pre><p>将派生类对象赋给基类对象将如何呢？（注意，这不同于将基类引用初始化为派生类对象）请看下面的例如：</p>
<pre><code>Brass blips;
BrassPlus snips(&quot;Raf Plosh&quot;, 911912, 600.0, 0.13);
blips = snips;
</code></pre><p>这将使用哪个赋值运算符呢？赋值语句将被转换成左边的对象调用的一个方法：</p>
<pre><code>blips.operator(snips);
</code></pre><p>其中做表面的对象是Brass对象，因此它将调用Brass::operator=(const Brass &amp;)函数。is-a关系允许Brass引用指向派生类对象，如snips。赋值运算符值处理基类成员，所以上述赋值操作将忽略snips的maxLoan成员和其他BrassPlus成员。总之，可以将派生类对象赋给基类对象，但这只涉及基类的成员。</p>
<p>相反的操作将如何呢？即可以将基类对象赋给派生类对象吗？请看下面的例如：</p>
<pre><code>Brass gp(&quot;Griff Hexbait&quot;, 21234&quot;, 1200);
BrassPlus temp;
temp = gp; 
</code></pre><p>上述赋值语句将被转换为如下所示：</p>
<pre><code>temp.operator=(gp);
</code></pre><p>左边的对象是BrassPlus对象，所以它调用BrassPlus::operator=(const BrassPlus &amp;)函数。然而，派生类引用不能自动引用基类对象，因此上述代码不能云三星，除非有下面的转换构造函数：</p>
<pre><code>BrassPlus(const Brass &amp;);
</code></pre><p>与BrassPlus类的情况类似，转换构造函数可以接受一个类型为基类的参数或其他参数，条件是其他参数有默认值：</p>
<pre><code>BrassPlus(const Brass &amp; ba, double ml = 500, double r = 0.1);
</code></pre><p>如果有转换构造函数，程序将通过它根据gp创建一个临时BrassPlus对象，然后将它用作赋值运算符的参数。</p>
<p>另一种方法是，顶一个用于基类赋给派生类的赋值运算符：</p>
<pre><code>BrassPlus &amp; BrassPlus::operator=(const Brass &amp; ba) {}
</code></pre><p>该赋值运算符的类型与赋值语句完全匹配，因此无需进行类型转换。</p>
<p>总之，问题“是否可以将基类对象赋给派生类对象？”的答案是“也许”。如果派生类包含了这样的构造函数，即对将基类转换为派生类对象进行了定义，则可以将基类对象赋给派生类对象。如果派生类定义了用于将基类对象赋给派生类对象的赋值运算符，则也可以这样做。如果上述两个条件都不满足，则不能这样做，除非使用显式强制类型转换。</p>
<p><strong>4. 私有成员和保护成员</strong></p>
<p>对于派生类对象，保护成员与类似于公有成员；但对于外部而言，保护成员与重游成员类似。派生类可以直接访问基类的保护成员，但只能通过记载的成员函数访问私有成员。因此，将基类成员设置为私有的可以提供安全性，而将它们设置为保护成员则可简化代码的编写工作，并提供访问速度。</p>
<p><strong>5. 虚方法</strong></p>
<p>设计基类时，必须确定是否将类声明为虚的。如果希望派生类能够重新定义方法，则应在基类中将方法定义为虚方法，这样可以启用晚期联编（动态联编）；如果不希望重新定义方法，则不必将其声明为虚的，这样虽然无法禁止他人重新定义方法，但表达了这样的意思：您不希望它被重新定义。</p>
<p>请注意，不适当的代码将阻止动态联编。例如，请看下面的两个函数：</p>
<pre><code>void Show(const Brass &amp; rba)
{
    rba.ViewAcct();
    cout &lt;&lt; endl;
}
void inadequate(Brass ba)
{
    ba.ViewAcct();
    cout &lt;&lt; endl;
}
</code></pre><p>第一个函数按引用传递对象，第二个按值传递对象。<br>现在，假设将派生类对象传递给上述两个函数：</p>
<pre><code>BrassPlus buzz(&quot;Buzz Parsec&quot;, 111122, 3300);
show(buzz);
inadequate(buzz);
</code></pre><p>show()函数调用使用rba参数成员BrassPlus对象buzz的引用，因此rba.ViewAcct()被解释为：BrassPlus版本，正如应该的那样。但在inadequate()函数中（按值传递对象），ba是Brass(const Brass&amp;)构造函数函数创建的一个Brass对象（自动向上强制类型转换使得构造函数参数可以引用一个BrassPlus对象）。因此，在inadequate()中，ba.ViewAcct()是Brass版本，所以只有buzz的Brass部分被显示。</p>
<p><strong>6. 析构函数</strong></p>
<p>基类的析构函数应该是虚的。这样，当通过指向对象的基类指针或引用来删除派生对象时，程序将首先调用派生类对象的析构函数，然后调用基类的析构函数，而不仅仅是调用基类的析构函数。</p>
<p><strong>7. 友元函数</strong></p>
<p>由于由于函数并非类成员，因此不能继承。然而，您可能希望派生类的友元函数能够使用基类的友元函数。为此，可以通过强制类型转换将派生类引用或指针转换为基类引用或指针，然后使用转换后的指针或引用来调用基类的友元函数：</p>
<pre><code>ostream &amp; operator&lt;&lt;(ostream &amp; os, const hasDMA &amp; rs)
{
    os &lt;&lt; (const baseDMA &amp;) rs;
    os &lt;&lt; &quot;Style: &quot; &lt;&lt; rs.style &lt;&lt; endl;
    return os;
}
</code></pre><p>也可以使用第15章将讨论的运算符dynamic_cast&lt;&gt;来进行强制类型转换：</p>
<pre><code>os &lt;&lt; dynamic_cast&lt;const baseDMA &amp;&gt; (rs);
</code></pre><p>鉴于第15章将讨论的原因，这是更佳的强制类型转换方式。</p>
<p><strong>8. 有关使用基类方法的说明</strong></p>
<p>以公有方式派生的类的对象可以通过多种方式来使用基类的方法：</p>
<ul>
<li>派生类对象自动使用继承而来的基类方法，如果派生类没有重新定义该方法。</li>
<li>派生类的构造函数自动调用基类的构造函数。</li>
<li>派生类的构造函数自动调用基类的默认构造函数，如果没有在成员初始化列表中指定其他构造函数。</li>
<li>派生类构造函数显式地调用成员初始化列表中指定的基类构造函数。</li>
<li>派生类方法可以使用作用域解析运算符来调用公有的和受保护的基类方法。</li>
<li>派生类的友元函数可以通过强制类型转换，将派生类引用或指针转换为基类引用或指针，然后使用该引用或指针来调用基类的友元函数。</li>
</ul>
<p><strong>9. 类函数小结</strong></p>
<p><img src="https://i.imgur.com/hEf8Ccr.png" alt=""></p>
<h2 id="13-9-总结"><a href="#13-9-总结" class="headerlink" title="13.9 总结"></a>13.9 总结</h2><p>is-a关系</p>
<p>不能继承基类的构造函数、析构函数和赋值运算符。</p>
<p>派生类可以直接访问基类的公有成员和保护成员，并能够通过基类的公有方法和保护方法访问基类的私有成员。</p>
<p>抽象基类ABC</p>
<p>虚函数</p>
<p>纯虚函数</p>

      
    </div>
    
    
    

    
      <div>
        <div id="wechat_subscriber" style="display: block; padding: 10px 0; margin: 20px auto; width: 100%; text-align: center">
    <img id="wechat_subscriber_qcode" src="/images/wechat-qcode.jpg" alt="zxp wechat" style="width: 200px; max-width: 100%;"/>
    <div>欢迎关注微信公众号！</div>
</div>

      </div>
    

    
      <div>
        <div style="padding: 10px 0; margin: 20px auto; width: 90%; text-align: center;">
  <div></div>
  <button id="rewardButton" disable="enable" onclick="var qr = document.getElementById('QR'); if (qr.style.display === 'none') {qr.style.display='block';} else {qr.style.display='none'}">
    <span>打赏</span>
  </button>
  <div id="QR" style="display: none;">

    
      <div id="wechat" style="display: inline-block">
        <img id="wechat_qr" src="/images/WeChatpay.jpg" alt="zxp 微信支付"/>
        <p>微信支付</p>
      </div>
    

    
      <div id="alipay" style="display: inline-block">
        <img id="alipay_qr" src="/images/Alipay.jpg" alt="zxp 支付宝"/>
        <p>支付宝</p>
      </div>
    

    

  </div>
</div>

      </div>
    

    
      <div>
        <ul class="post-copyright">
  <li class="post-copyright-author">
    <strong>本文作者：</strong>
    zxp
  </li>
  <li class="post-copyright-link">
    <strong>本文链接：</strong>
    <a href="https://zxpgo.github.io/2019/02/18/C++ Primer Plus--类继承（十三）/" title="C++ Primer Plus--类继承（十三）">https://zxpgo.github.io/2019/02/18/C++ Primer Plus--类继承（十三）/</a>
  </li>
  <li class="post-copyright-license">
    <strong>版权声明： </strong>
    本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/3.0/" rel="external nofollow" target="_blank">CC BY-NC-SA 3.0</a> 许可协议。转载请注明出处！
  </li>
</ul>

      </div>
    

    <footer class="post-footer">
      
        <div class="post-tags">
          
            <a href="/tag/C/" rel="tag"># C++</a>
          
        </div>
      

      
      
        <div class="post-widgets">
        

        

        
          
          <div id="needsharebutton-postbottom">
            <span class="btn">
              <i class="fa fa-share-alt" aria-hidden="true"></i>
            </span>
          </div>
        
        </div>
      
      

      
        <div class="post-nav">
          <div class="post-nav-next post-nav-item">
            
              <a href="/2019/02/16/廖雪峰SQL教程--关系模型(二)/" rel="next" title="廖雪峰SQL教程--关系模型(二)">
                <i class="fa fa-chevron-left"></i> 廖雪峰SQL教程--关系模型(二)
              </a>
            
          </div>

          <span class="post-nav-divider"></span>

          <div class="post-nav-prev post-nav-item">
            
              <a href="/2019/02/21/C++ Primer Plus--C++中的代码重用（十四）/" rel="prev" title="C++ Primer Plus--C++中的代码重用（十四）">
                C++ Primer Plus--C++中的代码重用（十四） <i class="fa fa-chevron-right"></i>
              </a>
            
          </div>
        </div>
      

      
      
    </footer>
  </div>
  
  
  
  </article>



    <div class="post-spread">
      
    </div>
  </div>


          </div>
          


          

  
    <div class="comments" id="comments">
      <div id="lv-container" data-id="city" data-uid="MTAyMC8zODgxNC8xNTM0Mg=="></div>
    </div>

  



        </div>
        
          
  
  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside id="sidebar" class="sidebar">
    
    <div class="sidebar-inner">

      

      
        <ul class="sidebar-nav motion-element">
          <li class="sidebar-nav-toc sidebar-nav-active" data-target="post-toc-wrap">
            文章目录
          </li>
          <li class="sidebar-nav-overview" data-target="site-overview-wrap">
            站点概览
          </li>
        </ul>
      

      <section class="site-overview-wrap sidebar-panel">
        <div class="site-overview">
          <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
            
              <img class="site-author-image" itemprop="image"
                src="/images/avatar.gif"
                alt="zxp" />
            
              <p class="site-author-name" itemprop="name">zxp</p>
              <p class="site-description motion-element" itemprop="description"></p>
          </div>

          <nav class="site-state motion-element">

            
              <div class="site-state-item site-state-posts">
              
                <a href="/archives/">
              
                  <span class="site-state-item-count">176</span>
                  <span class="site-state-item-name">日志</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-categories">
                <a href="/categories/index.html">
                  <span class="site-state-item-count">16</span>
                  <span class="site-state-item-name">分类</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-tags">
                <a href="/tags/index.html">
                  <span class="site-state-item-count">48</span>
                  <span class="site-state-item-name">标签</span>
                </a>
              </div>
            

          </nav>

          
            <div class="feed-link motion-element">
              <a href="/atom.xml" rel="alternate">
                <i class="fa fa-rss"></i>
                RSS
              </a>
            </div>
          

          
            <div class="links-of-author motion-element">
                
                  <span class="links-of-author-item">
                    <a href="https://blog.csdn.net/qq_25774883" target="_blank" title="CSDN">
                      
                        <i class="fa fa-fw fa-globe"></i>CSDN</a>
                  </span>
                
                  <span class="links-of-author-item">
                    <a href="https://github.com/zxpgo/zxpgo" target="_blank" title="GitHub">
                      
                        <i class="fa fa-fw fa-globe"></i>GitHub</a>
                  </span>
                
                  <span class="links-of-author-item">
                    <a href="https://www.linkedin.com/feed/" target="_blank" title="LinkedIn">
                      
                        <i class="fa fa-fw fa-globe"></i>LinkedIn</a>
                  </span>
                
                  <span class="links-of-author-item">
                    <a href="1165772354@qq.com" target="_blank" title="E-Mail">
                      
                        <i class="fa fa-fw fa-envelope"></i>E-Mail</a>
                  </span>
                
            </div>
          

          
          

          
          
            <div class="links-of-blogroll motion-element links-of-blogroll-inline">
              <div class="links-of-blogroll-title">
                <i class="fa  fa-fw fa-link"></i>
                友情链接
              </div>
              <ul class="links-of-blogroll-list">
                
                  <li class="links-of-blogroll-item">
                    <a href="http://theme-next.iissnan.com" title="Next主题" target="_blank">Next主题</a>
                  </li>
                
                  <li class="links-of-blogroll-item">
                    <a href="http://blog.rexking6.top" title="青爷博客" target="_blank">青爷博客</a>
                  </li>
                
                  <li class="links-of-blogroll-item">
                    <a href="https://me.csdn.net/download/qq_25774883" title="CSDN下载" target="_blank">CSDN下载</a>
                  </li>
                
                  <li class="links-of-blogroll-item">
                    <a href="https://www.livere.com/" title="来必力" target="_blank">来必力</a>
                  </li>
                
                  <li class="links-of-blogroll-item">
                    <a href="https://tongji.baidu.com/web/welcome/login" title="百度统计" target="_blank">百度统计</a>
                  </li>
                
                  <li class="links-of-blogroll-item">
                    <a href="https://leancloud.cn/" title="LeanCloud" target="_blank">LeanCloud</a>
                  </li>
                
                  <li class="links-of-blogroll-item">
                    <a href="http://ibruce.info/2015/04/04/busuanzi/" title="不蒜子" target="_blank">不蒜子</a>
                  </li>
                
                  <li class="links-of-blogroll-item">
                    <a href="https://leetcode-cn.com/" title="LeetCode" target="_blank">LeetCode</a>
                  </li>
                
              </ul>
            </div>
          

          

        </div>
      </section>

      
      <!--noindex-->
        <section class="post-toc-wrap motion-element sidebar-panel sidebar-panel-active">
          <div class="post-toc">

            
              
            

            
              <div class="post-toc-content"><ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#13-1-一个简单的基类"><span class="nav-number">1.</span> <span class="nav-text">13.1 一个简单的基类</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#13-1-1-派生一个类"><span class="nav-number">1.1.</span> <span class="nav-text">13.1.1 派生一个类</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#13-1-2-构造函数：访问权限的考虑"><span class="nav-number">1.2.</span> <span class="nav-text">13.1.2 构造函数：访问权限的考虑</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#13-1-3-使用派生类"><span class="nav-number">1.3.</span> <span class="nav-text">13.1.3 使用派生类</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#13-1-4-派生类和基类之间的特殊关系"><span class="nav-number">1.4.</span> <span class="nav-text">13.1.4 派生类和基类之间的特殊关系</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#13-2-继承：is-a关系"><span class="nav-number">2.</span> <span class="nav-text">13.2 继承：is-a关系</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#13-3-多态公有继承"><span class="nav-number">3.</span> <span class="nav-text">13.3 多态公有继承</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#13-3-1-开发Brass类和BrassPlus类"><span class="nav-number">3.1.</span> <span class="nav-text">13.3.1 开发Brass类和BrassPlus类</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#13-4-静态联编和动态联编"><span class="nav-number">4.</span> <span class="nav-text">13.4 静态联编和动态联编</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#13-4-1-指针和引用类型的兼容性"><span class="nav-number">4.1.</span> <span class="nav-text">13.4.1 指针和引用类型的兼容性</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#13-4-2-虚成员函数和动态联编"><span class="nav-number">4.2.</span> <span class="nav-text">13.4.2 虚成员函数和动态联编</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#13-4-3-有关虚函数注意事项"><span class="nav-number">4.3.</span> <span class="nav-text">13.4.3 有关虚函数注意事项</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#13-5-访问控制：-protected"><span class="nav-number">5.</span> <span class="nav-text">13.5 访问控制： protected</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#13-6-抽象基类"><span class="nav-number">6.</span> <span class="nav-text">13.6 抽象基类</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#13-6-1-应用ABC概念"><span class="nav-number">6.1.</span> <span class="nav-text">13.6.1 应用ABC概念</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#13-6-2-ABC理念"><span class="nav-number">6.2.</span> <span class="nav-text">13.6.2 ABC理念</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#13-7-继承和动态分配"><span class="nav-number">7.</span> <span class="nav-text">13.7 继承和动态分配</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#13-7-1-第一种情况：派生类不使用new"><span class="nav-number">7.1.</span> <span class="nav-text">13.7.1 第一种情况：派生类不使用new</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#12-7-2-第二种情况：派生类使用new"><span class="nav-number">7.2.</span> <span class="nav-text">12.7.2 第二种情况：派生类使用new</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#13-7-3-使用动态内存分配和友元的继承示例"><span class="nav-number">7.3.</span> <span class="nav-text">13.7.3 使用动态内存分配和友元的继承示例</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#13-8-类设计回顾"><span class="nav-number">8.</span> <span class="nav-text">13.8 类设计回顾</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#13-8-1-编译器生成的成员函数"><span class="nav-number">8.1.</span> <span class="nav-text">13.8.1 编译器生成的成员函数</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#13-8-2-其他的在方法"><span class="nav-number">8.2.</span> <span class="nav-text">13.8.2 其他的在方法</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#13-8-3-公有继承的考虑因素"><span class="nav-number">8.3.</span> <span class="nav-text">13.8.3 公有继承的考虑因素</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#13-9-总结"><span class="nav-number">9.</span> <span class="nav-text">13.9 总结</span></a></li></ol></div>
            

          </div>
        </section>
      <!--/noindex-->
      

      

    </div>
  </aside>


        
      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div>
<script async src="https//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
<i class="fa fa-user-md"></i><span id="busuanzi_container_site_pv" style='display:none'>
    本站总访问量 <span id="busuanzi_value_site_pv"></span> 
    <span class="post-meta-divider">|</span>
</span>
<span id="busuanzi_container_site_uv" style='display:none'>
    访问人数 <span id="busuanzi_value_site_uv"></span>
</span>
</div>


<script async src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>

<div class="copyright">&copy; 2018-8 &mdash; <span itemprop="copyrightYear">2020</span>
  <span class="with-love">
    <i class="fa fa-"></i> Power by 
  </span>
  <span class="author" itemprop="copyrightHolder">zxp</span>
  
  
</div>









        







        
      </div>
    </footer>

    
      <div class="back-to-top">
        <i class="fa fa-arrow-up"></i>
        
      </div>
    

    
      <div id="needsharebutton-float">
        <span class="btn">
          <i class="fa fa-share-alt" aria-hidden="true"></i>
        </span>
      </div>
    

  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>









  












  
  
    <script type="text/javascript" src="/lib/jquery/index.js?v=2.1.3"></script>
  

  
  
    <script type="text/javascript" src="/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>
  

  
  
    <script type="text/javascript" src="/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>
  


  


  <script type="text/javascript" src="/js/src/utils.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/motion.js?v=5.1.4"></script>



  
  


  <script type="text/javascript" src="/js/src/affix.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/schemes/pisces.js?v=5.1.4"></script>



  
  <script type="text/javascript" src="/js/src/scrollspy.js?v=5.1.4"></script>
<script type="text/javascript" src="/js/src/post-details.js?v=5.1.4"></script>



  


  <script type="text/javascript" src="/js/src/bootstrap.js?v=5.1.4"></script>



  


  




	





  




  
  <div id="lv-container" data-uid="MTAyMC8zODgxNC8xNTM0Mg==">
    <script type="text/javascript">
      (function(d, s) {
        var j, e = d.getElementsByTagName(s)[0];
        if (typeof LivereTower === 'function') { return; }
        j = d.createElement(s);
        j.src = 'https://cdn-city.livere.com/js/embed.dist.js';
        j.async = true;
        e.parentNode.insertBefore(j, e);
      })(document, 'script');
    </script>
	</div>
  











  

  <script type="text/javascript">
    // Popup Window;
    var isfetched = false;
    var isXml = true;
    // Search DB path;
    var search_path = "search.xml";
    if (search_path.length === 0) {
      search_path = "search.xml";
    } else if (/json$/i.test(search_path)) {
      isXml = false;
    }
    var path = "/" + search_path;
    // monitor main search box;

    var onPopupClose = function (e) {
      $('.popup').hide();
      $('#local-search-input').val('');
      $('.search-result-list').remove();
      $('#no-result').remove();
      $(".local-search-pop-overlay").remove();
      $('body').css('overflow', '');
    }

    function proceedsearch() {
      $("body")
        .append('<div class="search-popup-overlay local-search-pop-overlay"></div>')
        .css('overflow', 'hidden');
      $('.search-popup-overlay').click(onPopupClose);
      $('.popup').toggle();
      var $localSearchInput = $('#local-search-input');
      $localSearchInput.attr("autocapitalize", "none");
      $localSearchInput.attr("autocorrect", "off");
      $localSearchInput.focus();
    }

    // search function;
    var searchFunc = function(path, search_id, content_id) {
      'use strict';

      // start loading animation
      $("body")
        .append('<div class="search-popup-overlay local-search-pop-overlay">' +
          '<div id="search-loading-icon">' +
          '<i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>' +
          '</div>' +
          '</div>')
        .css('overflow', 'hidden');
      $("#search-loading-icon").css('margin', '20% auto 0 auto').css('text-align', 'center');

      $.ajax({
        url: path,
        dataType: isXml ? "xml" : "json",
        async: true,
        success: function(res) {
          // get the contents from search data
          isfetched = true;
          $('.popup').detach().appendTo('.header-inner');
          var datas = isXml ? $("entry", res).map(function() {
            return {
              title: $("title", this).text(),
              content: $("content",this).text(),
              url: $("url" , this).text()
            };
          }).get() : res;
          var input = document.getElementById(search_id);
          var resultContent = document.getElementById(content_id);
          var inputEventFunction = function() {
            var searchText = input.value.trim().toLowerCase();
            var keywords = searchText.split(/[\s\-]+/);
            if (keywords.length > 1) {
              keywords.push(searchText);
            }
            var resultItems = [];
            if (searchText.length > 0) {
              // perform local searching
              datas.forEach(function(data) {
                var isMatch = false;
                var hitCount = 0;
                var searchTextCount = 0;
                var title = data.title.trim();
                var titleInLowerCase = title.toLowerCase();
                var content = data.content.trim().replace(/<[^>]+>/g,"");
                var contentInLowerCase = content.toLowerCase();
                var articleUrl = decodeURIComponent(data.url);
                var indexOfTitle = [];
                var indexOfContent = [];
                // only match articles with not empty titles
                if(title != '') {
                  keywords.forEach(function(keyword) {
                    function getIndexByWord(word, text, caseSensitive) {
                      var wordLen = word.length;
                      if (wordLen === 0) {
                        return [];
                      }
                      var startPosition = 0, position = [], index = [];
                      if (!caseSensitive) {
                        text = text.toLowerCase();
                        word = word.toLowerCase();
                      }
                      while ((position = text.indexOf(word, startPosition)) > -1) {
                        index.push({position: position, word: word});
                        startPosition = position + wordLen;
                      }
                      return index;
                    }

                    indexOfTitle = indexOfTitle.concat(getIndexByWord(keyword, titleInLowerCase, false));
                    indexOfContent = indexOfContent.concat(getIndexByWord(keyword, contentInLowerCase, false));
                  });
                  if (indexOfTitle.length > 0 || indexOfContent.length > 0) {
                    isMatch = true;
                    hitCount = indexOfTitle.length + indexOfContent.length;
                  }
                }

                // show search results

                if (isMatch) {
                  // sort index by position of keyword

                  [indexOfTitle, indexOfContent].forEach(function (index) {
                    index.sort(function (itemLeft, itemRight) {
                      if (itemRight.position !== itemLeft.position) {
                        return itemRight.position - itemLeft.position;
                      } else {
                        return itemLeft.word.length - itemRight.word.length;
                      }
                    });
                  });

                  // merge hits into slices

                  function mergeIntoSlice(text, start, end, index) {
                    var item = index[index.length - 1];
                    var position = item.position;
                    var word = item.word;
                    var hits = [];
                    var searchTextCountInSlice = 0;
                    while (position + word.length <= end && index.length != 0) {
                      if (word === searchText) {
                        searchTextCountInSlice++;
                      }
                      hits.push({position: position, length: word.length});
                      var wordEnd = position + word.length;

                      // move to next position of hit

                      index.pop();
                      while (index.length != 0) {
                        item = index[index.length - 1];
                        position = item.position;
                        word = item.word;
                        if (wordEnd > position) {
                          index.pop();
                        } else {
                          break;
                        }
                      }
                    }
                    searchTextCount += searchTextCountInSlice;
                    return {
                      hits: hits,
                      start: start,
                      end: end,
                      searchTextCount: searchTextCountInSlice
                    };
                  }

                  var slicesOfTitle = [];
                  if (indexOfTitle.length != 0) {
                    slicesOfTitle.push(mergeIntoSlice(title, 0, title.length, indexOfTitle));
                  }

                  var slicesOfContent = [];
                  while (indexOfContent.length != 0) {
                    var item = indexOfContent[indexOfContent.length - 1];
                    var position = item.position;
                    var word = item.word;
                    // cut out 100 characters
                    var start = position - 20;
                    var end = position + 80;
                    if(start < 0){
                      start = 0;
                    }
                    if (end < position + word.length) {
                      end = position + word.length;
                    }
                    if(end > content.length){
                      end = content.length;
                    }
                    slicesOfContent.push(mergeIntoSlice(content, start, end, indexOfContent));
                  }

                  // sort slices in content by search text's count and hits' count

                  slicesOfContent.sort(function (sliceLeft, sliceRight) {
                    if (sliceLeft.searchTextCount !== sliceRight.searchTextCount) {
                      return sliceRight.searchTextCount - sliceLeft.searchTextCount;
                    } else if (sliceLeft.hits.length !== sliceRight.hits.length) {
                      return sliceRight.hits.length - sliceLeft.hits.length;
                    } else {
                      return sliceLeft.start - sliceRight.start;
                    }
                  });

                  // select top N slices in content

                  var upperBound = parseInt('1');
                  if (upperBound >= 0) {
                    slicesOfContent = slicesOfContent.slice(0, upperBound);
                  }

                  // highlight title and content

                  function highlightKeyword(text, slice) {
                    var result = '';
                    var prevEnd = slice.start;
                    slice.hits.forEach(function (hit) {
                      result += text.substring(prevEnd, hit.position);
                      var end = hit.position + hit.length;
                      result += '<b class="search-keyword">' + text.substring(hit.position, end) + '</b>';
                      prevEnd = end;
                    });
                    result += text.substring(prevEnd, slice.end);
                    return result;
                  }

                  var resultItem = '';

                  if (slicesOfTitle.length != 0) {
                    resultItem += "<li><a href='" + articleUrl + "' class='search-result-title'>" + highlightKeyword(title, slicesOfTitle[0]) + "</a>";
                  } else {
                    resultItem += "<li><a href='" + articleUrl + "' class='search-result-title'>" + title + "</a>";
                  }

                  slicesOfContent.forEach(function (slice) {
                    resultItem += "<a href='" + articleUrl + "'>" +
                      "<p class=\"search-result\">" + highlightKeyword(content, slice) +
                      "...</p>" + "</a>";
                  });

                  resultItem += "</li>";
                  resultItems.push({
                    item: resultItem,
                    searchTextCount: searchTextCount,
                    hitCount: hitCount,
                    id: resultItems.length
                  });
                }
              })
            };
            if (keywords.length === 1 && keywords[0] === "") {
              resultContent.innerHTML = '<div id="no-result"><i class="fa fa-search fa-5x" /></div>'
            } else if (resultItems.length === 0) {
              resultContent.innerHTML = '<div id="no-result"><i class="fa fa-frown-o fa-5x" /></div>'
            } else {
              resultItems.sort(function (resultLeft, resultRight) {
                if (resultLeft.searchTextCount !== resultRight.searchTextCount) {
                  return resultRight.searchTextCount - resultLeft.searchTextCount;
                } else if (resultLeft.hitCount !== resultRight.hitCount) {
                  return resultRight.hitCount - resultLeft.hitCount;
                } else {
                  return resultRight.id - resultLeft.id;
                }
              });
              var searchResultList = '<ul class=\"search-result-list\">';
              resultItems.forEach(function (result) {
                searchResultList += result.item;
              })
              searchResultList += "</ul>";
              resultContent.innerHTML = searchResultList;
            }
          }

          if ('auto' === 'auto') {
            input.addEventListener('input', inputEventFunction);
          } else {
            $('.search-icon').click(inputEventFunction);
            input.addEventListener('keypress', function (event) {
              if (event.keyCode === 13) {
                inputEventFunction();
              }
            });
          }

          // remove loading animation
          $(".local-search-pop-overlay").remove();
          $('body').css('overflow', '');

          proceedsearch();
        }
      });
    }

    // handle and trigger popup window;
    $('.popup-trigger').click(function(e) {
      e.stopPropagation();
      if (isfetched === false) {
        searchFunc(path, 'local-search-input', 'local-search-result');
      } else {
        proceedsearch();
      };
    });

    $('.popup-btn-close').click(onPopupClose);
    $('.popup').click(function(e){
      e.stopPropagation();
    });
    $(document).on('keyup', function (event) {
      var shouldDismissSearchPopup = event.which === 27 &&
        $('.search-popup').is(':visible');
      if (shouldDismissSearchPopup) {
        onPopupClose();
      }
    });
  </script>





  

  
  <script src="https://cdn1.lncld.net/static/js/av-core-mini-0.6.4.js"></script>
  <script>AV.initialize("2AyV3DKioBSdoryrFLRohzjB-gzGzoHsz", "XynedcHyJCVCrTfbD4yYnodo");</script>
  <script>
    function showTime(Counter) {
      var query = new AV.Query(Counter);
      var entries = [];
      var $visitors = $(".leancloud_visitors");

      $visitors.each(function () {
        entries.push( $(this).attr("id").trim() );
      });

      query.containedIn('url', entries);
      query.find()
        .done(function (results) {
          var COUNT_CONTAINER_REF = '.leancloud-visitors-count';

          if (results.length === 0) {
            $visitors.find(COUNT_CONTAINER_REF).text(0);
            return;
          }

          for (var i = 0; i < results.length; i++) {
            var item = results[i];
            var url = item.get('url');
            var time = item.get('time');
            var element = document.getElementById(url);

            $(element).find(COUNT_CONTAINER_REF).text(time);
          }
          for(var i = 0; i < entries.length; i++) {
            var url = entries[i];
            var element = document.getElementById(url);
            var countSpan = $(element).find(COUNT_CONTAINER_REF);
            if( countSpan.text() == '') {
              countSpan.text(0);
            }
          }
        })
        .fail(function (object, error) {
          console.log("Error: " + error.code + " " + error.message);
        });
    }

    function addCount(Counter) {
      var $visitors = $(".leancloud_visitors");
      var url = $visitors.attr('id').trim();
      var title = $visitors.attr('data-flag-title').trim();
      var query = new AV.Query(Counter);

      query.equalTo("url", url);
      query.find({
        success: function(results) {
          if (results.length > 0) {
            var counter = results[0];
            counter.fetchWhenSave(true);
            counter.increment("time");
            counter.save(null, {
              success: function(counter) {
                var $element = $(document.getElementById(url));
                $element.find('.leancloud-visitors-count').text(counter.get('time'));
              },
              error: function(counter, error) {
                console.log('Failed to save Visitor num, with error message: ' + error.message);
              }
            });
          } else {
            var newcounter = new Counter();
            /* Set ACL */
            var acl = new AV.ACL();
            acl.setPublicReadAccess(true);
            acl.setPublicWriteAccess(true);
            newcounter.setACL(acl);
            /* End Set ACL */
            newcounter.set("title", title);
            newcounter.set("url", url);
            newcounter.set("time", 1);
            newcounter.save(null, {
              success: function(newcounter) {
                var $element = $(document.getElementById(url));
                $element.find('.leancloud-visitors-count').text(newcounter.get('time'));
              },
              error: function(newcounter, error) {
                console.log('Failed to create');
              }
            });
          }
        },
        error: function(error) {
          console.log('Error:' + error.code + " " + error.message);
        }
      });
    }

    $(function() {
      var Counter = AV.Object.extend("Counter");
      if ($('.leancloud_visitors').length == 1) {
        addCount(Counter);
      } else if ($('.post-title-link').length > 1) {
        showTime(Counter);
      }
    });
  </script>



  

  
<script>
(function(){
    var bp = document.createElement('script');
    var curProtocol = window.location.protocol.split(':')[0];
    if (curProtocol === 'https') {
        bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';        
    }
    else {
        bp.src = 'http://push.zhanzhang.baidu.com/push.js';
    }
    var s = document.getElementsByTagName("script")[0];
    s.parentNode.insertBefore(bp, s);
})();
</script>


  
  
  
  <link rel="stylesheet" href="/lib/needsharebutton/needsharebutton.css">

  
  
  <script src="/lib/needsharebutton/needsharebutton.js"></script>

  <script>
    
      pbOptions = {};
      
          pbOptions.iconStyle = "default";
      
          pbOptions.boxForm = "vertical";
      
          pbOptions.position = "top";
      
          pbOptions.networks = "Weibo,Wechat,Douban,QQZone,Twitter,Facebook";
      
      new needShareButton('#needsharebutton-postbottom', pbOptions);
    
    
      flOptions = {};
      
          flOptions.iconStyle = "box";
      
          flOptions.boxForm = "horizontal";
      
          flOptions.position = "middleRight";
      
          flOptions.networks = "Weibo,Wechat,Douban,QQZone,Twitter,Facebook";
      
      new needShareButton('#needsharebutton-float', flOptions);
    
  </script>

  

  
  
    <script type="text/x-mathjax-config">
      MathJax.Hub.Config({
        tex2jax: {
          inlineMath: [ ['$','$'], ["\\(","\\)"]  ],
          processEscapes: true,
          skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
        }
      });
    </script>

    <script type="text/x-mathjax-config">
      MathJax.Hub.Queue(function() {
        var all = MathJax.Hub.getAllJax(), i;
        for (i=0; i < all.length; i += 1) {
          all[i].SourceElement().parentNode.className += ' has-jax';
        }
      });
    </script>
    <script type="text/javascript" src="//cdn.bootcss.com/mathjax/2.7.1/latest.js?config=TeX-AMS-MML_HTMLorMML"></script>
  


  
  <script type="text/javascript" src="/js/src/js.cookie.js?v=5.1.4"></script>
  <script type="text/javascript" src="/js/src/scroll-cookie.js?v=5.1.4"></script>


  
  <script type="text/javascript" src="/js/src/exturl.js?v=5.1.4"></script>


  
  
  	 <!-- custom analytics part create by xiamo -->
<script src="https://cdn1.lncld.net/static/js/av-core-mini-0.6.1.js"></script>
<script>AV.initialize("2AyV3DKioBSdoryrFLRohzjB-gzGzoHsz", "XynedcHyJCVCrTfbD4yYnodo");</script>
<script>
function showTime(Counter) {
	var query = new AV.Query(Counter);
	$(".leancloud_visitors").each(function() {
		var url = $(this).attr("id").trim();
		query.equalTo("url", url);
		query.find({
			success: function(results) {
				if (results.length == 0) {
					var content = $(document.getElementById(url)).text() + ' 0';
					$(document.getElementById(url)).text(content);
					return;
				}
				for (var i = 0; i < results.length; i++) {
					var object = results[i];
					var content = $(document.getElementById(url)).text() + ' ' + object.get('time');
					$(document.getElementById(url)).text(content);
				}
			}
		});

	});
}

</script>
  
</body>
</html>
